@lotics/app-sdk 0.100.1 → 0.101.1
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/AGENTS.md +32 -47
- package/dist/agent_stream.d.ts +131 -0
- package/dist/ask_ai.d.ts +27 -0
- package/dist/attachments.d.ts +58 -0
- package/dist/chunk-ARV5FAU5.js +1132 -0
- package/dist/comments.d.ts +89 -0
- package/dist/error_report.d.ts +9 -0
- package/dist/folder_pick.d.ts +8 -0
- package/dist/geolocation.d.ts +42 -0
- package/dist/hooks.d.ts +251 -0
- package/dist/{src/index.d.ts → index.d.ts} +13 -22
- package/dist/index.js +31331 -0
- package/dist/index.js.LEGAL.txt +11 -0
- package/dist/members.d.ts +32 -0
- package/dist/mock.d.ts +37 -0
- package/dist/mount.d.ts +19 -0
- package/dist/new_record.d.ts +37 -0
- package/dist/open_app.d.ts +12 -0
- package/dist/open_external.d.ts +10 -0
- package/dist/overlay.d.ts +25 -0
- package/dist/queries.d.ts +231 -0
- package/dist/recording.d.ts +47 -0
- package/dist/recording_state.d.ts +43 -0
- package/dist/rename_file.d.ts +13 -0
- package/dist/router.d.ts +10 -0
- package/dist/router.js +97 -0
- package/dist/row.d.ts +87 -0
- package/dist/rpc.d.ts +114 -0
- package/dist/select.d.ts +24 -0
- package/dist/shared_types.d.ts +8 -0
- package/dist/store.d.ts +43 -0
- package/dist/types.d.ts +36 -0
- package/dist/upload/optimize.d.ts +30 -0
- package/dist/upload/pipeline.d.ts +36 -0
- package/dist/upload/transport.d.ts +19 -0
- package/dist/url_params.d.ts +55 -0
- package/dist/use_recents.d.ts +15 -0
- package/dist/{src/use_url_state.d.ts → use_url_state.d.ts} +0 -2
- package/dist/viewer.d.ts +41 -0
- package/dist/written.d.ts +79 -0
- package/docs/ai.md +74 -133
- package/docs/data_fetching.md +209 -290
- package/docs/files.md +61 -51
- package/docs/members_and_options.md +92 -62
- package/docs/mutations.md +136 -205
- package/docs/navigation_and_state.md +26 -35
- package/docs/queries.md +144 -207
- package/docs/recipes.md +21 -45
- package/docs/runtime.md +74 -137
- package/docs/security.md +8 -11
- package/docs/workflows.md +189 -174
- package/package.json +27 -28
- package/dist/src/agent_stream.d.ts +0 -200
- package/dist/src/agent_stream.js +0 -314
- package/dist/src/ask_ai.d.ts +0 -40
- package/dist/src/ask_ai.js +0 -35
- package/dist/src/attachments.d.ts +0 -68
- package/dist/src/attachments.js +0 -93
- package/dist/src/comments.d.ts +0 -127
- package/dist/src/comments.js +0 -192
- package/dist/src/download.js +0 -54
- package/dist/src/geolocation.d.ts +0 -64
- package/dist/src/geolocation.js +0 -96
- package/dist/src/hooks.d.ts +0 -781
- package/dist/src/hooks.js +0 -860
- package/dist/src/index.js +0 -34
- package/dist/src/members.d.ts +0 -105
- package/dist/src/members.js +0 -62
- package/dist/src/mock.d.ts +0 -118
- package/dist/src/mock.js +0 -124
- package/dist/src/mount.d.ts +0 -47
- package/dist/src/mount.js +0 -34
- package/dist/src/new_record.d.ts +0 -74
- package/dist/src/new_record.js +0 -117
- package/dist/src/open_app.d.ts +0 -15
- package/dist/src/open_app.js +0 -18
- package/dist/src/open_external.d.ts +0 -16
- package/dist/src/open_external.js +0 -19
- package/dist/src/recording.d.ts +0 -59
- package/dist/src/recording.js +0 -30
- package/dist/src/recording_state.d.ts +0 -59
- package/dist/src/recording_state.js +0 -94
- package/dist/src/router.d.ts +0 -17
- package/dist/src/router.js +0 -144
- package/dist/src/row.d.ts +0 -159
- package/dist/src/row.js +0 -254
- package/dist/src/rpc.d.ts +0 -207
- package/dist/src/rpc.js +0 -904
- package/dist/src/select.d.ts +0 -48
- package/dist/src/select.js +0 -40
- package/dist/src/types.d.ts +0 -115
- package/dist/src/types.js +0 -1
- package/dist/src/upload/optimize.d.ts +0 -54
- package/dist/src/upload/optimize.js +0 -207
- package/dist/src/upload/pipeline.d.ts +0 -55
- package/dist/src/upload/pipeline.js +0 -52
- package/dist/src/upload/transport.d.ts +0 -42
- package/dist/src/upload/transport.js +0 -128
- package/dist/src/url_params.d.ts +0 -93
- package/dist/src/url_params.js +0 -215
- package/dist/src/use_optimistic.d.ts +0 -27
- package/dist/src/use_optimistic.js +0 -27
- package/dist/src/use_recents.d.ts +0 -19
- package/dist/src/use_recents.js +0 -71
- package/dist/src/use_url_state.js +0 -73
- package/dist/src/viewer.d.ts +0 -26
- package/dist/src/viewer.js +0 -47
- /package/dist/{src/download.d.ts → download.d.ts} +0 -0
package/dist/src/hooks.d.ts
DELETED
|
@@ -1,781 +0,0 @@
|
|
|
1
|
-
import { type ImageFidelity } from "./upload/optimize.js";
|
|
2
|
-
import { type AttachmentsOptions, type AttachmentsState } from "./attachments.js";
|
|
3
|
-
import { type AiContextValue } from "./rpc.js";
|
|
4
|
-
import { type AgentUIPart, type PendingChoice, type AgentRunLanding } from "./agent_stream.js";
|
|
5
|
-
import type { AppWorkflows, AppWorkflowResults, AppQueries, AppQueryColumns, AppAgents, AppAgentResults } from "./types.js";
|
|
6
|
-
import type { ResolvedMember } from "./members.js";
|
|
7
|
-
import type { ResolvedOption } from "./select.js";
|
|
8
|
-
export type { AgentRunState, AgentUIPart, PendingChoice, ChoiceQuestion, ChoiceOption, AskUserChoiceOutput, AgentRunLanding } from "./agent_stream.js";
|
|
9
|
-
export { buildChoiceOutput } from "./agent_stream.js";
|
|
10
|
-
/**
|
|
11
|
-
* One row of a query result whose COLUMNS are not known: a dynamic alias, or a
|
|
12
|
-
* query whose AST names no projection (a bare `from_table`). Where codegen could
|
|
13
|
-
* read them, `RowOf` states them instead and there is no index signature — see
|
|
14
|
-
* that type, which is where the reason lives.
|
|
15
|
-
*
|
|
16
|
-
* The projected values stay `unknown` either way: a column's TYPE is the field's
|
|
17
|
-
* and the manifest does not carry it, so the cell readers (`row.text`,
|
|
18
|
-
* `readLinks`, `readFiles`, …) are what narrow them.
|
|
19
|
-
*
|
|
20
|
-
* The `__source_*` columns are different in kind: the compiler injects them at
|
|
21
|
-
* every layer, and they are the only way to address the RECORD a row came from
|
|
22
|
-
* — what `useComments`, a workflow's `record_id` input, and `useAiContext`'s
|
|
23
|
-
* record refs all need. Typed as `unknown` they were unusable without a cast,
|
|
24
|
-
* which is how this SDK's own examples came to show code that does not compile.
|
|
25
|
-
*
|
|
26
|
-
* They are OPTIONAL, and that is the honest shape rather than a hedge: a
|
|
27
|
-
* grouped query collapses rows, so its output has no originating record and the
|
|
28
|
-
* compiler emits no addressing columns for it (the platform's own `record_id`
|
|
29
|
-
* filter refuses such a query for exactly this reason). Narrow before use —
|
|
30
|
-
* on an aggregate row these are genuinely absent, not merely unproven.
|
|
31
|
-
*
|
|
32
|
-
* The line for what belongs here is "consumed RAW", not "starts with
|
|
33
|
-
* `__source_`". A row also carries `__source_locked`, `__created_at` /
|
|
34
|
-
* `__updated_at` and per-projection `__src_field_*`, and each of those has a
|
|
35
|
-
* reader that owns its decoding (`readLocked`, the `row.*` helpers) — a reader
|
|
36
|
-
* IS the narrowing, so a type here would duplicate it. Only these two are
|
|
37
|
-
* handed straight to a `record_id` / `table_id` parameter with nothing in
|
|
38
|
-
* between, which is why only these two needed a type.
|
|
39
|
-
*/
|
|
40
|
-
export interface QueryRow {
|
|
41
|
-
[column: string]: unknown;
|
|
42
|
-
/** The record this row came from. Absent on a grouped/aggregated row. */
|
|
43
|
-
__source_record_id?: string;
|
|
44
|
-
/** The table that record lives in. Absent on a grouped/aggregated row. */
|
|
45
|
-
__source_table_id?: string;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* The row type of alias `K`: the columns that alias PROJECTS, plus the two
|
|
49
|
-
* addressing columns, and nothing else.
|
|
50
|
-
*
|
|
51
|
-
* `.lotics/app_queries.d.ts` already names every projected column
|
|
52
|
-
* (`AppQueryColumns`), read off the query's AST by the same rule the server
|
|
53
|
-
* names them by. A read is bounded to that union for the same reason a
|
|
54
|
-
* `filter`/`sort` key is — except the failure is quieter. A key the query does
|
|
55
|
-
* not carry is refused by the server; a COLUMN it does not carry comes back
|
|
56
|
-
* `undefined`, which every cell reader answers with its empty value, so a
|
|
57
|
-
* misspelt column, a renamed field and a projection moved to another alias all
|
|
58
|
-
* render as a blank cell in a member's browser with no error anywhere. Removing
|
|
59
|
-
* the index signature is what turns that silence into a `tsc` error, which is
|
|
60
|
-
* what `lotics app check` refuses to ship.
|
|
61
|
-
*
|
|
62
|
-
* An alias with no `AppQueryColumns` entry keeps `QueryRow` — a union that could
|
|
63
|
-
* be wrong is worse than none, the rule `ColumnKeyOf` already follows.
|
|
64
|
-
*/
|
|
65
|
-
export type RowOf<K extends string> = K extends keyof AppQueryColumns ? {
|
|
66
|
-
[C in AppQueryColumns[K] & string]: unknown;
|
|
67
|
-
} & Pick<QueryRow, "__source_record_id" | "__source_table_id"> : QueryRow;
|
|
68
|
-
/** Fields shared by every query hook's return value. */
|
|
69
|
-
interface QueryStateBase {
|
|
70
|
-
/**
|
|
71
|
-
* True only on the initial load — a request is in flight and there are no
|
|
72
|
-
* rows yet. Stays false during background revalidation and while typing a new
|
|
73
|
-
* query (the previous rows remain visible), so consumers never blank data to a
|
|
74
|
-
* spinner on refetch. Use `isValidating` for a subtle refetch indicator.
|
|
75
|
-
*/
|
|
76
|
-
loading: boolean;
|
|
77
|
-
/** True whenever any request is in flight (initial load or revalidation). */
|
|
78
|
-
isValidating: boolean;
|
|
79
|
-
error: string | null;
|
|
80
|
-
/**
|
|
81
|
-
* Re-run the query. Use after a known mutation point — a successful
|
|
82
|
-
* `useWorkflow(alias)()` call — to pull the latest state.
|
|
83
|
-
*/
|
|
84
|
-
refetch: () => void;
|
|
85
|
-
}
|
|
86
|
-
/** Return value of `useQuery` — a single fetch, no pagination. */
|
|
87
|
-
interface QueryState<R> extends QueryStateBase {
|
|
88
|
-
rows: R[];
|
|
89
|
-
/**
|
|
90
|
-
* True when the limit cut the result — more rows matched than arrived, so
|
|
91
|
-
* `rows` is SHORT and anything folded from it (a total, a ratio, a KPI strip)
|
|
92
|
-
* is under-reported. Without it the cap is invisible: no error, no empty
|
|
93
|
-
* state, just numbers that look right.
|
|
94
|
-
*
|
|
95
|
-
* The limit is your `pageSize` when you set one and the server's row cap
|
|
96
|
-
* otherwise. False while loading and under a fixture.
|
|
97
|
-
*/
|
|
98
|
-
truncated: boolean;
|
|
99
|
-
}
|
|
100
|
-
/** Return value of `useInfiniteQuery` — append/load-more. */
|
|
101
|
-
interface InfiniteQueryState<R> extends QueryStateBase {
|
|
102
|
-
/** All loaded pages, flattened and accumulated. */
|
|
103
|
-
rows: R[];
|
|
104
|
-
/**
|
|
105
|
-
* Fetch the next page and append it to `rows`. No-op when there are no more
|
|
106
|
-
* rows (`hasMore` is false). `loadingMore` is true while it runs.
|
|
107
|
-
*/
|
|
108
|
-
loadMore: () => void;
|
|
109
|
-
/** True when the last page came back full, so more rows may exist. */
|
|
110
|
-
hasMore: boolean;
|
|
111
|
-
/** True while a `loadMore` request is in flight. */
|
|
112
|
-
loadingMore: boolean;
|
|
113
|
-
}
|
|
114
|
-
/** Return value of `usePaginatedQuery` — page-model with a total. */
|
|
115
|
-
interface PaginatedQueryState<R> extends QueryStateBase {
|
|
116
|
-
/** Rows of the current page only (≤ `pageSize`). */
|
|
117
|
-
rows: R[];
|
|
118
|
-
/** Total rows in the filtered set (across all pages). `undefined` until the
|
|
119
|
-
* count resolves. */
|
|
120
|
-
total: number | undefined;
|
|
121
|
-
/** `ceil(total / pageSize)`, or `undefined` until the count resolves. */
|
|
122
|
-
totalPages: number | undefined;
|
|
123
|
-
/** Current 0-indexed page. */
|
|
124
|
-
page: number;
|
|
125
|
-
pageSize: number;
|
|
126
|
-
/** True when a next page exists. */
|
|
127
|
-
hasMore: boolean;
|
|
128
|
-
/** Jump to a page (0-indexed). Clamped at 0. */
|
|
129
|
-
setPage: (page: number) => void;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* One sort key — the wire shape of the query RPC's `sort`. The server applies
|
|
133
|
-
* these AFTER the named query, bounded to the query's output columns (an
|
|
134
|
-
* un-projected `field_key` is rejected), so an app can sort by any column it
|
|
135
|
-
* actually selects without the query template declaring it.
|
|
136
|
-
*/
|
|
137
|
-
export interface QuerySortKey<C extends string = string> {
|
|
138
|
-
/** One of the query's projected outputs — `AppQueryColumns[alias]` on a
|
|
139
|
-
* typed alias, so a column the query does not carry is a compile error
|
|
140
|
-
* rather than the server's request-time refusal. */
|
|
141
|
-
field_key: C;
|
|
142
|
-
order: "asc" | "desc";
|
|
143
|
-
/**
|
|
144
|
-
* Where rows BLANK in this column sit — `"bottom"` when omitted, which is what
|
|
145
|
-
* you want for "the ones with a value first".
|
|
146
|
-
*
|
|
147
|
-
* It earns its place on a MULTI-KEY sort over columns that are blank by
|
|
148
|
-
* position rather than by accident — a pipeline's per-step date stamps, say,
|
|
149
|
-
* where the first non-blank column IS the row's rank. Reading such a ladder
|
|
150
|
-
* from the top with blanks at the bottom orders it furthest-along-first; the
|
|
151
|
-
* reverse reading needs blanks on TOP, and with only the default there is no
|
|
152
|
-
* way to express it, so the column can be sorted one way and not the other.
|
|
153
|
-
*/
|
|
154
|
-
blank_position?: "top" | "bottom";
|
|
155
|
-
}
|
|
156
|
-
/** A filter condition over one output column (wire shape of a filter node).
|
|
157
|
-
* `C` is the key's type — see `QuerySortKey`. */
|
|
158
|
-
export interface QueryFilterFieldCondition<C extends string = string> {
|
|
159
|
-
node_type: "condition";
|
|
160
|
-
field_key: C;
|
|
161
|
-
type?: string;
|
|
162
|
-
operator: string;
|
|
163
|
-
value?: unknown;
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* A condition on the row's OWN id rather than on a field — the fetch-by-id
|
|
167
|
-
* filter. It carries no `field_key` because a record has no field holding its
|
|
168
|
-
* own id, and the engine dispatches on `type` before it reads one.
|
|
169
|
-
*/
|
|
170
|
-
export interface QueryFilterRecordIdCondition {
|
|
171
|
-
node_type: "condition";
|
|
172
|
-
type: "record_id";
|
|
173
|
-
operator: string;
|
|
174
|
-
value?: unknown;
|
|
175
|
-
}
|
|
176
|
-
export type QueryFilterCondition<C extends string = string> = QueryFilterFieldCondition<C> | QueryFilterRecordIdCondition;
|
|
177
|
-
/** A boolean group of filter nodes (wire shape — recursive). */
|
|
178
|
-
export interface QueryFilterGroup<C extends string = string> {
|
|
179
|
-
node_type: "group";
|
|
180
|
-
logic: "and" | "or";
|
|
181
|
-
children: Array<QueryFilterCondition<C> | QueryFilterGroup<C>>;
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Runtime filter applied AFTER the named query, bounded to its output columns
|
|
185
|
-
* (same exposure invariant as `sort`) — at compile time through `C`, and again
|
|
186
|
-
* on the server. Build a group from per-column filters with
|
|
187
|
-
* `columnFilterToConditions` (`@lotics/ui/column_filter`).
|
|
188
|
-
*/
|
|
189
|
-
export type QueryFilter<C extends string = string> = QueryFilterCondition<C> | QueryFilterGroup<C>;
|
|
190
|
-
/**
|
|
191
|
-
* The type a runtime `filter`/`sort` key takes on alias `K`: the union codegen
|
|
192
|
-
* wrote into `AppQueryColumns`, else `string` (the server's check is then the
|
|
193
|
-
* only one). A computed key must be narrowed to the union to compile.
|
|
194
|
-
*/
|
|
195
|
-
export type ColumnKeyOf<K extends string> = K extends keyof AppQueryColumns ? AppQueryColumns[K] & string : string;
|
|
196
|
-
/** Options shared by every query hook. `C` is the filter/sort key's type — see
|
|
197
|
-
* `ColumnKeyOf`. */
|
|
198
|
-
export interface BaseQueryOptions<C extends string = string> {
|
|
199
|
-
/**
|
|
200
|
-
* When `false`, the query does not run: `rows` stays empty, `loading` is
|
|
201
|
-
* false, and no request is sent. Flip it back to `true` to fetch. This is the
|
|
202
|
-
* primitive for search-as-you-type (skip until the user types) and for detail
|
|
203
|
-
* queries (skip until a row is selected) — a parameterized search filter
|
|
204
|
-
* matches everything on an empty term, so an always-on query would dump the
|
|
205
|
-
* whole table on first paint. Default `true`.
|
|
206
|
-
*/
|
|
207
|
-
enabled?: boolean;
|
|
208
|
-
/**
|
|
209
|
-
* When `false`, the query does not auto-refetch on window focus / tab return /
|
|
210
|
-
* network reconnect (`refetch()` still works). Default `true`, right for
|
|
211
|
-
* dashboards that should stay fresh. Set `false` for transient queries — a
|
|
212
|
-
* search bound to an ephemeral term, or on-demand detail — where a refocus
|
|
213
|
-
* re-run is wasted work and a visible reload.
|
|
214
|
-
*/
|
|
215
|
-
revalidateOnFocus?: boolean;
|
|
216
|
-
/**
|
|
217
|
-
* Sort the result by output columns at runtime. Changing it re-queries (it is
|
|
218
|
-
* part of the cache key). Empty/omitted leaves the query's own order intact.
|
|
219
|
-
*/
|
|
220
|
-
sort?: QuerySortKey<C>[];
|
|
221
|
-
/**
|
|
222
|
-
* Filter the result by output columns at runtime. Changing it re-queries.
|
|
223
|
-
* Compose from per-column UI filters via `columnFilterToConditions`.
|
|
224
|
-
*/
|
|
225
|
-
filter?: QueryFilter<C>;
|
|
226
|
-
}
|
|
227
|
-
/** Options for `useQuery` — a single fetch. */
|
|
228
|
-
export interface QueryOptions<C extends string = string> extends BaseQueryOptions<C> {
|
|
229
|
-
/** Max rows to fetch in the one request (a cap, not pagination). The server
|
|
230
|
-
* still clamps to its own maximum. Omit to fetch up to the server cap. */
|
|
231
|
-
pageSize?: number;
|
|
232
|
-
}
|
|
233
|
-
/** Options for `useInfiniteQuery` — append/load-more. */
|
|
234
|
-
export interface InfiniteQueryOptions<C extends string = string> extends BaseQueryOptions<C> {
|
|
235
|
-
/** Rows per page. `loadMore()` appends the next page. */
|
|
236
|
-
pageSize: number;
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Options for `useCount` — one number, no rows.
|
|
240
|
-
*
|
|
241
|
-
* `sort` and `pageSize` are absent rather than ignored. A count is a single-row
|
|
242
|
-
* COUNT over the filtered set; ordering it and paginating it are meaningless,
|
|
243
|
-
* and an option a hook silently drops is worse than one that will not compile.
|
|
244
|
-
*/
|
|
245
|
-
export type CountOptions<C extends string = string> = Omit<BaseQueryOptions<C>, "sort">;
|
|
246
|
-
/** Return value of `useCount` — the size of a filtered set, and nothing else. */
|
|
247
|
-
interface CountState extends QueryStateBase {
|
|
248
|
-
/** Rows in the filtered set. `undefined` until the count resolves. */
|
|
249
|
-
total: number | undefined;
|
|
250
|
-
}
|
|
251
|
-
/** Options for `usePaginatedQuery` — page-model with a total. */
|
|
252
|
-
export interface PaginatedQueryOptions<C extends string = string> extends BaseQueryOptions<C> {
|
|
253
|
-
/** Rows per page. Default 25. */
|
|
254
|
-
pageSize?: number;
|
|
255
|
-
/**
|
|
256
|
-
* The total, when the SCREEN already knows it — which suppresses the hook's
|
|
257
|
-
* own count request entirely.
|
|
258
|
-
*
|
|
259
|
-
* Three states, all of them in the type: **omitted** → the hook counts;
|
|
260
|
-
* **`null`** → yours, not resolved yet; **a number** → yours, use it. `null`
|
|
261
|
-
* is what makes the option usable at all, because the natural source is an
|
|
262
|
-
* aggregate that is still loading on the first render — with only
|
|
263
|
-
* "number-or-nothing" the hook would fire the count it exists to avoid and
|
|
264
|
-
* throw the result away the moment the real number landed. So read it as
|
|
265
|
-
* `?? null`:
|
|
266
|
-
*
|
|
267
|
-
* ```tsx
|
|
268
|
-
* const summary = useQuery("orderStats", params); // one ungrouped aggregate
|
|
269
|
-
* const rows = usePaginatedQuery("orders", params, {
|
|
270
|
-
* pageSize: 100,
|
|
271
|
-
* total: (summary.rows[0]?.row_count as number | undefined) ?? null,
|
|
272
|
-
* });
|
|
273
|
-
* ```
|
|
274
|
-
*
|
|
275
|
-
* Until the number arrives the hook reports `total: undefined` and `hasMore`
|
|
276
|
-
* falls back to "the page came back full" — exactly its behaviour while a
|
|
277
|
-
* count is in flight.
|
|
278
|
-
*
|
|
279
|
-
* Worth reaching for when a screen already renders the same figure: a `count`
|
|
280
|
-
* request re-executes the whole named query server-side, so a summary reading
|
|
281
|
-
* "N items" beside a table of those N rows is otherwise paying for that number
|
|
282
|
-
* twice, and both executions grow with the filtered set.
|
|
283
|
-
*
|
|
284
|
-
* The number must count the SAME set the query returns — the hook derives
|
|
285
|
-
* `totalPages` and `hasMore` from it and cannot tell that it doesn't.
|
|
286
|
-
*
|
|
287
|
-
* `refetch()` does not refresh it; it is yours, so refresh its source.
|
|
288
|
-
*/
|
|
289
|
-
total?: number | null;
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* Trigger a workflow by alias from the app's manifest.
|
|
293
|
-
*
|
|
294
|
-
* The alias must be declared in `package.json` "lotics.workflows" and synced
|
|
295
|
-
* to `apps.workflows` on the last deploy.
|
|
296
|
-
*
|
|
297
|
-
* Per-app CLI codegen (`lotics app pull` / `app dev` / `app deploy`) writes
|
|
298
|
-
* `.lotics/app_workflows.d.ts` augmenting `AppWorkflows` with the declared
|
|
299
|
-
* alias → input-type map. Result:
|
|
300
|
-
* - Undeclared alias → compile-time error at the `useWorkflow("...")` site
|
|
301
|
-
* - Declared with full `{workflow_id, inputs}` form → callable typed as
|
|
302
|
-
* `(inputs: <DeclaredShape>) => Promise<WorkflowResult>`
|
|
303
|
-
* - Declared with shorthand (bare workflow_id) → callable typed as
|
|
304
|
-
* `(inputs?: Record<string, unknown>) => Promise<WorkflowResult>` (untyped inputs)
|
|
305
|
-
*
|
|
306
|
-
* ```tsx
|
|
307
|
-
* const issue = useWorkflow("issueInvoiceStorageDrop");
|
|
308
|
-
* // Guard the addressing column: a grouped query's rows carry none, and the
|
|
309
|
-
* // workflow would be handed `undefined` where it declares a record.
|
|
310
|
-
* if (row.__source_record_id) await issue({ record_id: row.__source_record_id });
|
|
311
|
-
* ```
|
|
312
|
-
*/
|
|
313
|
-
export declare function useWorkflow<K extends keyof AppWorkflows & string>(alias: K): UseWorkflowFn<K>;
|
|
314
|
-
export declare function useWorkflow(alias: string): (inputs?: Record<string, unknown>) => Promise<WorkflowResult>;
|
|
315
|
-
type ResultDataOf<K extends string> = K extends keyof AppWorkflowResults ? AppWorkflowResults[K] : unknown;
|
|
316
|
-
type UseWorkflowFn<K extends keyof AppWorkflows & string> = AppWorkflows[K] extends Record<string, unknown> ? AppWorkflows[K] extends Record<string, never> ? (inputs?: Record<string, never>) => Promise<WorkflowResult<ResultDataOf<K>>> : (inputs: AppWorkflows[K]) => Promise<WorkflowResult<ResultDataOf<K>>> : (inputs?: Record<string, unknown>) => Promise<WorkflowResult<ResultDataOf<K>>>;
|
|
317
|
-
/**
|
|
318
|
-
* Result of an app-workflow run — the execute endpoint's response. `files` holds
|
|
319
|
-
* any document a workflow step generated (e.g. via a `generate_*_from_template`
|
|
320
|
-
* tool), resolved for download: read `files[0].url` and pass it to `openExternal`.
|
|
321
|
-
* A workflow that generates no file resolves with `files` absent.
|
|
322
|
-
*
|
|
323
|
-
* `data` is the structured value the workflow returned via `return({ data })`,
|
|
324
|
-
* typed per the alias's declared `outputs` schema (`unknown` when none was declared).
|
|
325
|
-
*
|
|
326
|
-
* A transport/gateway failure (a Cloudflare 524 timeout on a long run, any 5xx,
|
|
327
|
-
* or a non-JSON error page) **resolves** with `{ status: "error", message }` —
|
|
328
|
-
* a body-free, friendly message — rather than rejecting with a raw HTML body.
|
|
329
|
-
* So an app handles every failure (handled workflow error AND transport error)
|
|
330
|
-
* by checking `result.status === "error"`; it never receives gateway HTML.
|
|
331
|
-
*/
|
|
332
|
-
export interface WorkflowResult<TData = unknown> {
|
|
333
|
-
status: "success" | "error";
|
|
334
|
-
message?: string;
|
|
335
|
-
files?: UploadedFile[];
|
|
336
|
-
data?: TData;
|
|
337
|
-
/**
|
|
338
|
-
* Per-input refusals, keyed by the INPUT name the alias declares — what a
|
|
339
|
-
* form wires straight onto the control that is wrong
|
|
340
|
-
* (`<FormField error={result.field_errors?.ly_do}>`), where `message` can
|
|
341
|
-
* only say it at the dialog's scope.
|
|
342
|
-
*
|
|
343
|
-
* Either half of the round trip fills it: the SERVER, when the payload does
|
|
344
|
-
* not match what the alias declares, and the workflow's own
|
|
345
|
-
* `return({ field_errors })`. One key, so a screen wires the control once
|
|
346
|
-
* rather than branching on which half refused. A step whose write the
|
|
347
|
-
* target table refuses (its `before_*` workflow, a unique tuple) keys its
|
|
348
|
-
* sentence by the FIELD key (`fld_…`) instead, since the table has never
|
|
349
|
-
* heard of the input. Absent when nothing named a field — and an older
|
|
350
|
-
* server names none.
|
|
351
|
-
*/
|
|
352
|
-
field_errors?: Record<string, string>;
|
|
353
|
-
}
|
|
354
|
-
/**
|
|
355
|
-
* The (params, opts) tail of a query hook for alias `K`: `params` optional when
|
|
356
|
-
* the alias declares none, required otherwise, then an optional `opts` — a
|
|
357
|
-
* variadic tuple so `f("a", { … })` and `f("a", params, { … })` both type-check.
|
|
358
|
-
* ONE conditional signature, never a typed overload beside a loose
|
|
359
|
-
* `alias: string` one: a rejected filter key would fall through to the loose
|
|
360
|
-
* overload and compile.
|
|
361
|
-
*/
|
|
362
|
-
type QueryArgs<K extends string, O> = K extends keyof AppQueries ? AppQueries[K] extends Record<string, never> ? [params?: Record<string, never>, opts?: O] : [params: AppQueries[K], opts?: O] : [params?: Record<string, unknown>, opts?: O];
|
|
363
|
-
/**
|
|
364
|
-
* Read rows from a query the app's author declared in `lotics.queries` — a
|
|
365
|
-
* single fetch, no pagination. For long lists use `usePaginatedQuery`
|
|
366
|
-
* (numbered pages + total) or `useInfiniteQuery` (load-more).
|
|
367
|
-
*
|
|
368
|
-
* The app never sends a raw query AST — it invokes a named query by alias and
|
|
369
|
-
* fills the template's declared `{{params.x}}` value holes. The server holds
|
|
370
|
-
* the canonical AST; this is what bounds a public app's data exposure to
|
|
371
|
-
* exactly the queries the manifest declares.
|
|
372
|
-
*
|
|
373
|
-
* ```tsx
|
|
374
|
-
* const { rows, loading } = useQuery("openOrders", { status: "open" });
|
|
375
|
-
* ```
|
|
376
|
-
*
|
|
377
|
-
* Per-app CLI codegen writes `.lotics/app_queries.d.ts` augmenting `AppQueries`
|
|
378
|
-
* with the declared alias → param-type map, so an undeclared alias is a
|
|
379
|
-
* compile-time error and params are typed per the manifest. The same file names
|
|
380
|
-
* the alias's projected columns, which is what bounds a ROW read — see `RowOf`.
|
|
381
|
-
*/
|
|
382
|
-
export declare function useQuery<K extends string>(alias: K, ...args: QueryArgs<K, QueryOptions<ColumnKeyOf<K>>>): QueryState<RowOf<K>>;
|
|
383
|
-
/** The resolved option set of one select column, plus an index for value
|
|
384
|
-
* rendering. The companion to a query row, for select fields. */
|
|
385
|
-
export interface FieldOptions {
|
|
386
|
-
/** The source field's display name — e.g. a picker/section label. */
|
|
387
|
-
label: string;
|
|
388
|
-
/**
|
|
389
|
-
* Every option of the field — `{ key, label, color, mark? }`. Includes options not
|
|
390
|
-
* present in any current row, so a freshly-added option appears in a picker
|
|
391
|
-
* without an app change, and a removed one drops out.
|
|
392
|
-
*/
|
|
393
|
-
options: ResolvedOption[];
|
|
394
|
-
/**
|
|
395
|
-
* Resolve one option by key — for COLORING A STORED VALUE: pair with
|
|
396
|
-
* `readSelect(cell)[0]?.key`. `undefined` for an unknown key (option removed
|
|
397
|
-
* after the cell was written); render the cell's own label with a neutral
|
|
398
|
-
* badge in that case.
|
|
399
|
-
*/
|
|
400
|
-
byKey: (key: string) => ResolvedOption | undefined;
|
|
401
|
-
}
|
|
402
|
-
/** Return value of `useFieldOptions`. `C` is the column key's type — the alias's
|
|
403
|
-
* projected outputs, see `ColumnKeyOf`. */
|
|
404
|
-
export interface FieldOptionsState<C extends string = string> {
|
|
405
|
-
/**
|
|
406
|
-
* Resolved option sets keyed by the query's OUTPUT column name — the alias's
|
|
407
|
-
* own columns, so a key belonging to another query is a `tsc` error rather
|
|
408
|
-
* than a `[]` picker nobody can open.
|
|
409
|
-
*
|
|
410
|
-
* EVERY key is optional, and that is the shape rather than a hedge: a select
|
|
411
|
-
* column the server could not resolve to a source field (a UNION output whose
|
|
412
|
-
* arms disagree, a computed column) is genuinely absent, and a non-select
|
|
413
|
-
* column never had options. Read through it (`fields.status?.options ?? []`).
|
|
414
|
-
*/
|
|
415
|
-
fields: Partial<Record<C, FieldOptions>>;
|
|
416
|
-
loading: boolean;
|
|
417
|
-
isValidating: boolean;
|
|
418
|
-
error: string | null;
|
|
419
|
-
/** Re-fetch — after a known field-config change (rare). */
|
|
420
|
-
refetch: () => void;
|
|
421
|
-
}
|
|
422
|
-
/** Options for `useFieldOptions`. */
|
|
423
|
-
export interface FieldOptionsOptions {
|
|
424
|
-
/** Defer the fetch until true — e.g. a picker that only needs options once an
|
|
425
|
-
* edit drawer opens. Defaults to true. */
|
|
426
|
-
enabled?: boolean;
|
|
427
|
-
}
|
|
428
|
-
/**
|
|
429
|
-
* Resolve the full option set (key, label, color) of a named query's `select`
|
|
430
|
-
* columns — the picker companion to `useQuery`. Where a query CELL carries only
|
|
431
|
-
* the options a record actually holds (key + label, no color), this returns each
|
|
432
|
-
* select column's COMPLETE option list with colors, straight from the field
|
|
433
|
-
* config — so it populates a dropdown AND colors a stored value, and a freshly
|
|
434
|
-
* added/removed option flows through with no app change.
|
|
435
|
-
*
|
|
436
|
-
* Addressed by the same alias you query: the option sets resolve from the named
|
|
437
|
-
* query's output columns, scoped exactly like running it, and `fields` is keyed
|
|
438
|
-
* by that same union — the alias that CARRIES the column is the one to ask, and
|
|
439
|
-
* asking another is a compile error instead of an empty picker. A column the
|
|
440
|
-
* server can't map to a source select field (UNION output, computed column) is
|
|
441
|
-
* absent, so every key is optional.
|
|
442
|
-
*
|
|
443
|
-
* ```tsx
|
|
444
|
-
* const { fields } = useFieldOptions("records");
|
|
445
|
-
* // populate + color a picker:
|
|
446
|
-
* <Select variant="native" options={fields.status?.options ?? []}
|
|
447
|
-
* renderOptionContent={(o) => <Status option={o} />} />
|
|
448
|
-
* // color a stored value:
|
|
449
|
-
* <Status option={fields.status?.byKey(readSelect(r.status)[0]?.key ?? "")} />
|
|
450
|
-
* ```
|
|
451
|
-
*/
|
|
452
|
-
export declare function useFieldOptions<K extends keyof AppQueries & string>(alias: K, opts?: FieldOptionsOptions): FieldOptionsState<ColumnKeyOf<K>>;
|
|
453
|
-
export declare function useFieldOptions(alias: string, opts?: FieldOptionsOptions): FieldOptionsState;
|
|
454
|
-
/**
|
|
455
|
-
* Like `useQuery` but append/load-more: the first render loads one page and
|
|
456
|
-
* `loadMore()` appends the next, accumulating into `rows` (infinite scroll).
|
|
457
|
-
* For numbered pages + a total, use `usePaginatedQuery`.
|
|
458
|
-
*
|
|
459
|
-
* ```tsx
|
|
460
|
-
* const { rows, loadMore, hasMore } = useInfiniteQuery("feed", {}, { pageSize: 30 });
|
|
461
|
-
* ```
|
|
462
|
-
*/
|
|
463
|
-
export declare function useInfiniteQuery<K extends string>(alias: K, ...args: QueryArgs<K, InfiniteQueryOptions<ColumnKeyOf<K>>>): InfiniteQueryState<RowOf<K>>;
|
|
464
|
-
/**
|
|
465
|
-
* Page-model query with a total — the data hook behind a numbered, jumpable
|
|
466
|
-
* table (pairs with `@lotics/ui/pagination`). It owns the page
|
|
467
|
-
* cursor and fetches two things: the current page of rows, and a `count` over
|
|
468
|
-
* the filtered set (keyed independently of page + sort, so paging and
|
|
469
|
-
* re-sorting never recount). The `(params, filter)` tuple is the result-set
|
|
470
|
-
* identity: changing it resets to page 0 AND recounts; changing only `sort`
|
|
471
|
-
* does neither.
|
|
472
|
-
*
|
|
473
|
-
* ```tsx
|
|
474
|
-
* const { rows, total, page, setPage, hasMore } =
|
|
475
|
-
* usePaginatedQuery("orders", { q }, { pageSize: 25, sort, filter });
|
|
476
|
-
* ```
|
|
477
|
-
*/
|
|
478
|
-
export declare function usePaginatedQuery<K extends string>(alias: K, ...args: QueryArgs<K, PaginatedQueryOptions<ColumnKeyOf<K>>>): PaginatedQueryState<RowOf<K>>;
|
|
479
|
-
/**
|
|
480
|
-
* HOW MANY rows a query matches — one number, no rows fetched.
|
|
481
|
-
*
|
|
482
|
-
* The shape behind a facet chip, a queue badge, a "N awaiting approval" tile:
|
|
483
|
-
* the screen wants the size of a set it is not listing. Reach for it instead of
|
|
484
|
-
* the two things that used to stand in for it, both of which are worse:
|
|
485
|
-
*
|
|
486
|
-
* - `usePaginatedQuery(alias, params, { pageSize: 1 })` buys a row nobody
|
|
487
|
-
* renders — two requests for one integer, against a server that bounds how
|
|
488
|
-
* many app queries run at once.
|
|
489
|
-
* - A hand-rolled `rpc("query", { count: true })` is one request and leaves the
|
|
490
|
-
* cache: it does not dedupe with the page beside it, does not revalidate on
|
|
491
|
-
* focus, and never hears the host's post-write refetch. The number then goes
|
|
492
|
-
* stale over a set that has moved while everything around it updates, which
|
|
493
|
-
* is the failure worth avoiding — a count that is quietly wrong costs more
|
|
494
|
-
* than a count that costs a request.
|
|
495
|
-
*
|
|
496
|
-
* ONE COUNT PER FILTERED SET. The cache key is `(alias, params, filter)` — the
|
|
497
|
-
* very key `usePaginatedQuery` counts under — so a table and a badge over the
|
|
498
|
-
* same set issue ONE count between them, and a page click or a re-sort reuses
|
|
499
|
-
* it (a count is sort- and page-independent).
|
|
500
|
-
*
|
|
501
|
-
* **N counts over one source should not be N hooks.** Each declared query
|
|
502
|
-
* re-executes its whole `from` tree, so four facets mounted as four `useCount`s
|
|
503
|
-
* are four full scans that land in the same burst. When the counts differ only
|
|
504
|
-
* by a bucket the rows can be grouped on, one `group` query returns them all in
|
|
505
|
-
* a single scan and folds client-side (queries.md §10) — and its figure can be
|
|
506
|
-
* handed to `usePaginatedQuery`'s `total` so the list stops counting too. This
|
|
507
|
-
* hook is for the count that has no sibling to group with.
|
|
508
|
-
*
|
|
509
|
-
* ```tsx
|
|
510
|
-
* const { total } = useCount("orders", { q }, { filter: unpaidFilter });
|
|
511
|
-
* return <Status label={total == null ? "…" : `${total}`} />;
|
|
512
|
-
* ```
|
|
513
|
-
*/
|
|
514
|
-
export declare function useCount<K extends string>(alias: K, ...args: QueryArgs<K, CountOptions<ColumnKeyOf<K>>>): CountState;
|
|
515
|
-
/** A file the host has stored and resolved serving URLs for. */
|
|
516
|
-
export interface UploadedFile {
|
|
517
|
-
id: string;
|
|
518
|
-
filename: string;
|
|
519
|
-
mime_type: string;
|
|
520
|
-
url?: string;
|
|
521
|
-
thumbnail_url?: string;
|
|
522
|
-
}
|
|
523
|
-
interface FileUploadState {
|
|
524
|
-
/**
|
|
525
|
-
* Upload one file. Resolves to the stored file; pass `UploadedFile.id` into
|
|
526
|
-
* a `useWorkflow` call to attach it to a record. Rejects on failure — the
|
|
527
|
-
* file is never partially stored.
|
|
528
|
-
*
|
|
529
|
-
* `fidelity` says how much of the image must survive storage; it defaults to
|
|
530
|
-
* `"high"`, which keeps the text of a photographed document legible. Only
|
|
531
|
-
* photographs are affected — a PDF, Word or Excel file is stored untouched at
|
|
532
|
-
* every step. Pass `"standard"` for bulk visual capture (a forty-photo survey,
|
|
533
|
-
* where the volume is what costs you), or `"original"` when the pixels
|
|
534
|
-
* themselves are the evidence.
|
|
535
|
-
*/
|
|
536
|
-
upload: (file: File, options?: {
|
|
537
|
-
fidelity?: ImageFidelity;
|
|
538
|
-
}) => Promise<UploadedFile>;
|
|
539
|
-
/** True while any upload from this hook is in flight. */
|
|
540
|
-
uploading: boolean;
|
|
541
|
-
/** Message of the most recent failed upload, cleared when a new one starts. */
|
|
542
|
-
error: string | null;
|
|
543
|
-
}
|
|
544
|
-
/**
|
|
545
|
-
* Upload files from an app. The bytes are stored via a presigned
|
|
546
|
-
* direct-to-storage upload the host mediates; the API server never proxies
|
|
547
|
-
* them. Works the same in a public (anonymous) app and a member-facing one.
|
|
548
|
-
*
|
|
549
|
-
* ```tsx
|
|
550
|
-
* const { upload, uploading } = useFileUpload();
|
|
551
|
-
* const submit = useWorkflow("submitApplication");
|
|
552
|
-
* const cccd = await upload(file);
|
|
553
|
-
* await submit({ ...fields, cccd_file_id: cccd.id });
|
|
554
|
-
* ```
|
|
555
|
-
*/
|
|
556
|
-
export declare function useFileUpload(): FileUploadState;
|
|
557
|
-
/**
|
|
558
|
-
* Attachments with the optimistic-preview UX: a local object-URL preview shows
|
|
559
|
-
* the INSTANT a file is added, the upload runs in the background, and the
|
|
560
|
-
* stored `file_id` lands in `files` when it completes. Picking is the app's
|
|
561
|
-
* (button / paste / drop) — pass the resulting `File[]` to `add`, or to
|
|
562
|
-
* `attach` where a write has to wait for the ids.
|
|
563
|
-
*
|
|
564
|
-
* WHICH LIFECYCLE is the caller's one decision. A composer accumulates and
|
|
565
|
-
* clears when it sends; a record's section passes what its row now holds as
|
|
566
|
-
* `landed`, and each entry leaves the queue as the pile takes it over.
|
|
567
|
-
*
|
|
568
|
-
* ```tsx
|
|
569
|
-
* const { files, add, remove, clear, uploading, fileIds } = useAttachments();
|
|
570
|
-
* const design = useWorkflow("design");
|
|
571
|
-
* // attach: <Button icon="paperclip" onPress={() => pickFiles({ accept: "image/*" }).then(add)} />
|
|
572
|
-
* // preview: map each AttachedFile to a @lotics/ui DisplayFile (snake_case → camelCase) — the
|
|
573
|
-
* // app owns this data→UI adapter; the SDK never imports @lotics/ui:
|
|
574
|
-
* // files.map((f) => (
|
|
575
|
-
* // <FileThumbnail
|
|
576
|
-
* // file={{ id: f.id, filename: f.filename, mimeType: f.mime_type, url: f.preview_url }}
|
|
577
|
-
* // uploading={f.status === "uploading"} onRemove={() => remove(f.id)} />
|
|
578
|
-
* // ))
|
|
579
|
-
* // send: design({ photo: fileIds[0] }); clear();
|
|
580
|
-
* ```
|
|
581
|
-
*
|
|
582
|
-
* The queue is `useAttachmentQueue`, bound here to the app's own upload.
|
|
583
|
-
*/
|
|
584
|
-
export declare function useAttachments(options?: AttachmentsOptions): AttachmentsState;
|
|
585
|
-
/**
|
|
586
|
-
* Publish a slice of the CURRENT SCREEN's view state to the app's ambient chat
|
|
587
|
-
* agent, so a member chatting alongside the app gets an agent that knows what
|
|
588
|
-
* they are looking at — which list is filtered to what, which record is open,
|
|
589
|
-
* what is typed into a form. Declarative and lifecycle-bound: mounting or
|
|
590
|
-
* changing `context` pushes it to the host; unmounting, renaming the `slot`, or
|
|
591
|
-
* passing `null` clears it. Independent components may publish different `slot`s
|
|
592
|
-
* concurrently (a list screen + an open detail drawer); the newest value per
|
|
593
|
-
* slot wins.
|
|
594
|
-
*
|
|
595
|
-
* **Push-only, and a SNAPSHOT of what the app already RENDERED to this member** —
|
|
596
|
-
* never a channel for chat to pull app-authority data. `records` are passed as
|
|
597
|
-
* raw `{ table_id, record_id }` refs (unresolved); the member's own chat agent
|
|
598
|
-
* acts on them only where that member's IAM already allows. The host feeds
|
|
599
|
-
* `description`/`data` into the agent's prompt as clearly-labeled DATA, never as
|
|
600
|
-
* instructions.
|
|
601
|
-
*
|
|
602
|
-
* Host-enforced caps (exceeding them truncates/drops — never an error): `slot`
|
|
603
|
-
* ≤ 50 chars; `description` ≤ 1000 chars (truncated with "…"); `records` ≤ 20;
|
|
604
|
-
* `data` must JSON-serialize to ≤ 2000 chars or the `data` field is dropped (the
|
|
605
|
-
* description is kept); ≤ 8 slots per app (a 9th evicts the least-recently
|
|
606
|
-
* updated).
|
|
607
|
-
*
|
|
608
|
-
* ```tsx
|
|
609
|
-
* useAiContext("orders_list", {
|
|
610
|
-
* description: `Viewing ${rows.length} orders filtered to status=open, sorted by due date.`,
|
|
611
|
-
* // `flatMap` + the guard, not `map`: the addressing columns are absent on a
|
|
612
|
-
* // GROUPED query's rows, so a ref built without checking carries `undefined`
|
|
613
|
-
* // and points the agent at nothing.
|
|
614
|
-
* records: rows.flatMap((r) =>
|
|
615
|
-
* r.__source_table_id && r.__source_record_id
|
|
616
|
-
* ? [{ table_id: r.__source_table_id, record_id: r.__source_record_id }]
|
|
617
|
-
* : [],
|
|
618
|
-
* ),
|
|
619
|
-
* data: { filter: "status=open", sort: "due_date desc" },
|
|
620
|
-
* });
|
|
621
|
-
* ```
|
|
622
|
-
*
|
|
623
|
-
* No-ops with no embedding host (standalone on the app's own origin — there is no
|
|
624
|
-
* chat surface to inform) and in mock mode. Since 0.52.
|
|
625
|
-
*/
|
|
626
|
-
export declare function useAiContext(slot: string, context: AiContextValue | null): void;
|
|
627
|
-
interface MembersState {
|
|
628
|
-
/** Members of the app's organization, for assign / member-picker UIs. */
|
|
629
|
-
members: ResolvedMember[];
|
|
630
|
-
loading: boolean;
|
|
631
|
-
error: string | null;
|
|
632
|
-
}
|
|
633
|
-
/** Options for `useMembers`. */
|
|
634
|
-
export interface MembersOptions {
|
|
635
|
-
/**
|
|
636
|
-
* Restrict to one member group, as `GRP.<group>` from `.lotics/app_fields.ts`
|
|
637
|
-
* (a pasted `grp_…` id is refused by `lotics app check` — it resolves to
|
|
638
|
-
* nothing in a copy of the app). The group must be declared on a `member`
|
|
639
|
-
* workflow input's `group` — listing an undeclared group errors. Omit to list
|
|
640
|
-
* the whole org roster.
|
|
641
|
-
*/
|
|
642
|
-
group?: string;
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
* List the members of the app's organization — the candidate set for an
|
|
646
|
-
* "assign to a member" picker. Each member is `{ id, name, email, image }`
|
|
647
|
-
* (`image` = avatar URL, may be null). Resolves through the host (member-only;
|
|
648
|
-
* an anonymous public visitor gets an error). Names may be empty for members
|
|
649
|
-
* without a display name set — fall back to `email`.
|
|
650
|
-
*
|
|
651
|
-
* Gated: the app must DECLARE that it works with members — it needs a workflow
|
|
652
|
-
* whose manifest declares a `member`-typed input. Passing `{ group }` restricts
|
|
653
|
-
* to that group, and is only honored if some member input declares that
|
|
654
|
-
* `group` — so an app can only list (and assign into) groups it declares.
|
|
655
|
-
*
|
|
656
|
-
* ```tsx
|
|
657
|
-
* const { members } = useMembers({ group: GRP.sale });
|
|
658
|
-
* // <Select variant="native" options={members.map((m) => ({
|
|
659
|
-
* // value: m.id, label: m.name || m.email || m.id, image: m.image,
|
|
660
|
-
* // }))} />
|
|
661
|
-
* ```
|
|
662
|
-
*/
|
|
663
|
-
export declare function useMembers(opts?: MembersOptions): MembersState;
|
|
664
|
-
type AgentOutputOf<K extends string> = K extends keyof AppAgentResults ? AppAgentResults[K] : unknown;
|
|
665
|
-
/** Options for one `run(...)` call — the app-owned session key it belongs to. */
|
|
666
|
-
export interface AgentRunOptions {
|
|
667
|
-
/** Groups this run with prior runs in the same working session; the agent
|
|
668
|
-
* re-reads them for context. Mint a new id to "clear context". */
|
|
669
|
-
sessionId: string;
|
|
670
|
-
/** Deliberately abort the run in flight and start this one in its place.
|
|
671
|
-
* Without it, `run()` is SINGLE-FLIGHT: a call while a run is streaming
|
|
672
|
-
* returns the in-flight run's promise instead of starting (and billing) a
|
|
673
|
-
* second run — so an accidental double-press resolves with the first run's
|
|
674
|
-
* result. The aborted-and-replaced run still executes and bills server-side;
|
|
675
|
-
* replacement is a deliberate act, never a side effect of an extra click. */
|
|
676
|
-
replace?: boolean;
|
|
677
|
-
}
|
|
678
|
-
/** The live state + controls returned by `useAgentRun`. */
|
|
679
|
-
export interface UseAgentRun<TInput, TOutput> {
|
|
680
|
-
/** Start a run — streams progress into this hook's state and resolves with how
|
|
681
|
-
* the leg ENDED (`settled` / `parked` / `failed` / `aborted`). Read `kind`
|
|
682
|
-
* rather than the hook's state: this value is a snapshot at settle, whereas
|
|
683
|
-
* the state has not committed yet when the promise resolves. SINGLE-FLIGHT —
|
|
684
|
-
* a second call while one is streaming joins the first (see `replace`). */
|
|
685
|
-
run: (input: TInput, opts: AgentRunOptions) => Promise<AgentRunLanding<TOutput>>;
|
|
686
|
-
/** Stop listening locally (no server effect) — the run keeps executing
|
|
687
|
-
* server-side and its result lands in the session history. Used on unmount. */
|
|
688
|
-
abort: () => void;
|
|
689
|
-
/** Explicitly stop the run server-side (saves tokens) AND locally. Wire a
|
|
690
|
-
* user-facing "Stop" button to this, not `abort`. */
|
|
691
|
-
cancel: () => void;
|
|
692
|
-
/** `awaiting_input` = the run is PARKED on a question the agent asked
|
|
693
|
-
* (`pendingChoice` carries it); `answerChoice` continues the run. */
|
|
694
|
-
status: "idle" | "streaming" | "awaiting_input" | "completed" | "error";
|
|
695
|
-
/** The ordered run transcript as ai-sdk `parts` — prose interleaved with tool
|
|
696
|
-
* calls, in stream order. Feed straight to `@lotics/ui` `AgentRun`:
|
|
697
|
-
* `<AgentRun parts={run.parts} state={run.status === "streaming" ? "streaming" : run.status === "error" ? "error" : "done"} />`
|
|
698
|
-
* — no hand-assembly. */
|
|
699
|
-
parts: AgentUIPart[];
|
|
700
|
-
/** The agent's pending ask — non-null exactly while `status` is
|
|
701
|
-
* `awaiting_input`. `questions` maps 1:1 onto `@lotics/ui`'s `ClarifyWizard`
|
|
702
|
-
* (`{question, options: {label, description}[], allow_custom}`). */
|
|
703
|
-
pendingChoice: PendingChoice | null;
|
|
704
|
-
/** Answer the pending ask and CONTINUE the run — one `{value, custom}` per
|
|
705
|
-
* question, aligned by index (exactly what `ClarifyWizard`'s `onSubmit`
|
|
706
|
-
* yields). Streams the continuation into the same `parts` and resolves like
|
|
707
|
-
* `run` — a landing, which may be `parked` again for a follow-up ask. Rejects
|
|
708
|
-
* when nothing is pending, or when the server refuses the answer (the run
|
|
709
|
-
* stays parked; show the error and keep the wizard open). */
|
|
710
|
-
answerChoice: (answers: {
|
|
711
|
-
value: string;
|
|
712
|
-
custom: boolean;
|
|
713
|
-
}[]) => Promise<AgentRunLanding<TOutput>>;
|
|
714
|
-
/** The agent's ANSWER prose (every `text` part concatenated), accumulating live —
|
|
715
|
-
* excludes thinking (`reasoning` is its own part). For a FREE-TEXT agent this IS
|
|
716
|
-
* the result; a structured agent's result is `output`. Derived from `parts`. */
|
|
717
|
-
text: string;
|
|
718
|
-
/** The structured result once the run completes — the agent's `submit_result`
|
|
719
|
-
* output. `undefined` when the run produced none (a free-text agent, or one that
|
|
720
|
-
* finished without submitting); a free-text answer lives in `text`, never here.
|
|
721
|
-
* So it is safe to treat a present `output` as the declared shape — but still
|
|
722
|
-
* validate untrusted inner fields (the model authored the `submit_result` body,
|
|
723
|
-
* so e.g. an array field may be missing/mistyped). */
|
|
724
|
-
output?: TOutput;
|
|
725
|
-
error?: string;
|
|
726
|
-
}
|
|
727
|
-
/**
|
|
728
|
-
* Run a streaming agent declared in `package.json` lotics.agents and invoked by
|
|
729
|
-
* alias. `run(input, { sessionId })` starts it; progress streams into `status` +
|
|
730
|
-
* the ordered `parts` transcript (feed straight to `@lotics/ui` `AgentRun`). A
|
|
731
|
-
* STRUCTURED agent's result lands in `output`; a FREE-TEXT agent's answer is the
|
|
732
|
-
* transcript's prose (`text`). Read the session's history with `useAgentRuns`.
|
|
733
|
-
*
|
|
734
|
-
* ```tsx
|
|
735
|
-
* const recognize = useAgentRun("recognize");
|
|
736
|
-
* await recognize.run({ image_file_id }, { sessionId });
|
|
737
|
-
* // <AgentRun parts={recognize.parts} state={recognize.status === "streaming" ? "streaming" : "done"} />
|
|
738
|
-
* // then read recognize.output (structured) or recognize.text (free-text)
|
|
739
|
-
* ```
|
|
740
|
-
*/
|
|
741
|
-
export declare function useAgentRun<K extends keyof AppAgents & string>(alias: K): UseAgentRun<AppAgents[K], AgentOutputOf<K>>;
|
|
742
|
-
export declare function useAgentRun(alias: string): UseAgentRun<Record<string, unknown>, unknown>;
|
|
743
|
-
/** One persisted run in a session's history (the `/agent-runs` row shape). */
|
|
744
|
-
export interface AgentRunRecord {
|
|
745
|
-
id: string;
|
|
746
|
-
agent_alias: string;
|
|
747
|
-
session_id: string;
|
|
748
|
-
status: string;
|
|
749
|
-
input: Record<string, unknown> | null;
|
|
750
|
-
output: unknown;
|
|
751
|
-
error_message: string | null;
|
|
752
|
-
started_at: string;
|
|
753
|
-
completed_at: string | null;
|
|
754
|
-
/** Single-run GET only, while `status` is "awaiting_input": the pending ask
|
|
755
|
-
* derived server-side from the transcript — lets a reconnecting client
|
|
756
|
-
* rebuild the question without the live stream. */
|
|
757
|
-
pending_interactive?: {
|
|
758
|
-
tool_call_id: string;
|
|
759
|
-
tool_name: string;
|
|
760
|
-
input: unknown;
|
|
761
|
-
} | null;
|
|
762
|
-
}
|
|
763
|
-
interface AgentRunsState {
|
|
764
|
-
runs: AgentRunRecord[];
|
|
765
|
-
loading: boolean;
|
|
766
|
-
error: string | null;
|
|
767
|
-
refetch: () => void;
|
|
768
|
-
}
|
|
769
|
-
/**
|
|
770
|
-
* The run history of a session, oldest-first — the persisted outputs the app
|
|
771
|
-
* renders as a session log (NOT a chat: a flat list of past runs). Refetch
|
|
772
|
-
* after a `run(...)` completes to pull in the new one.
|
|
773
|
-
*
|
|
774
|
-
* ```tsx
|
|
775
|
-
* const { runs } = useAgentRuns(sessionId);
|
|
776
|
-
* ```
|
|
777
|
-
*/
|
|
778
|
-
export declare function useAgentRuns(sessionId: string, opts?: {
|
|
779
|
-
enabled?: boolean;
|
|
780
|
-
revalidateOnFocus?: boolean;
|
|
781
|
-
}): AgentRunsState;
|