@lotics/app-sdk 0.87.9 → 0.88.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/AGENTS.md +1 -1
- package/dist/src/hooks.d.ts +45 -29
- package/dist/src/index.d.ts +2 -2
- package/dist/src/types.d.ts +20 -0
- package/docs/data_fetching.md +30 -10
- package/docs/files.md +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -16,7 +16,7 @@ signature; open the file.**
|
|
|
16
16
|
|---|---|
|
|
17
17
|
| [docs/recipes.md](./docs/recipes.md) | Task-shaped how-tos for the actions whose mechanism is not guessable from the hooks — returning a generated file, returning structured data, parameterized lookups, composable optional filters, cell decoding, testing an AI action without spending credits. |
|
|
18
18
|
| [docs/queries.md](./docs/queries.md) | **The query engine authoring reference** — AST node kinds, per-field-type operator support, filters/params/pruning, free-text search, combining tables (join/union/link/`unnest`/`record_id`), shaping (aggregates, date buckets, windows), runtime refinement bounds, limits & the efficiency playbook. |
|
|
19
|
-
| [docs/data_fetching.md](./docs/data_fetching.md) | The four read hooks (`useQuery`/`useInfiniteQuery`/`usePaginatedQuery`/`useCount` — the last for a number with no rows, sharing the `(alias, params, filter)` count key the paginated hook uses, so a list and a badge over one set buy one count; a count is a full scan and stays its OWN request so rows paint without waiting for it, and `rows.length` is never a count since rows truncate silently at 10,000), the `QueryRow` shape (projected columns `unknown`; `__source_record_id`/`__source_table_id` typed but optional), cell readers (`row.*`, `readSelect`, `readMembers`, `readLinks`, `readFiles`, `readLocked`), `useFieldOptions`, caching — **arrival revalidates** (a re-mount renders cache *and* refreshes it in the background, `loading` never flips) — **realtime push** (a table one of your queries reads changes and that query refetches within about a second, alias-precise, records-only, host-embedded apps only) — and the fourth source, **this app's own successful write**, which re-reads every mounted query immediately rather than waiting on that push (→ [mutations](./docs/mutations.md)), data discipline, the pagination count as a second full execution (and `total` to suppress it), the search-as-you-type + record-picker patterns. |
|
|
19
|
+
| [docs/data_fetching.md](./docs/data_fetching.md) | The four read hooks (`useQuery`/`useInfiniteQuery`/`usePaginatedQuery`/`useCount` — the last for a number with no rows, sharing the `(alias, params, filter)` count key the paginated hook uses, so a list and a badge over one set buy one count; a count is a full scan and stays its OWN request so rows paint without waiting for it, and `rows.length` is never a count since rows truncate silently at 10,000), the `QueryRow` shape (projected columns `unknown`; `__source_record_id`/`__source_table_id` typed but optional), runtime `sort`/`filter` keys typed against the query's own projection (`AppQueryColumns`, `ColumnKeyOf`), cell readers (`row.*`, `readSelect`, `readMembers`, `readLinks`, `readFiles`, `readLocked`), `useFieldOptions`, caching — **arrival revalidates** (a re-mount renders cache *and* refreshes it in the background, `loading` never flips) — **realtime push** (a table one of your queries reads changes and that query refetches within about a second, alias-precise, records-only, host-embedded apps only) — and the fourth source, **this app's own successful write**, which re-reads every mounted query immediately rather than waiting on that push (→ [mutations](./docs/mutations.md)), data discipline, the pagination count as a second full execution (and `total` to suppress it), the search-as-you-type + record-picker patterns. |
|
|
20
20
|
| [docs/mutations.md](./docs/mutations.md) | `useWorkflow` (the ONLY write path), the `WorkflowResult` resolve-never-throw contract, typed inputs, the automatic re-read a SUCCESSFUL write triggers over every mounted query (so a screen never waits on the host push to see its own write), diff-before-update, locked records, `useOptimistic`, `useNewRecord` (client-minted `rec_*` id so a new-record surface never remounts on its first save), read-after-write ordering (a re-read must not overtake an in-flight write). |
|
|
21
21
|
| [docs/workflows.md](./docs/workflows.md) | **The workflow-BODY authoring reference** — the JS subset a `src/workflows/<alias>.ts` body may use: the parse-at-save/never-execute model, opaque `fld_*`/`opt_*` keys, expression sources + link descent, every step form (tool call, `agent`, waits, `validate`, `return`), the accepted sugar and its canonical lowering, helpers + callback rules, record-write surfaces, the traps, the bright line, and the verify loop — `check` (the only local gate: the app's own `npm run typecheck` never sees a body) → `dry_run_workflow` (static green is not a run) → `set`. |
|
|
22
22
|
| [docs/files.md](./docs/files.md) | Files end to end — `useFileUpload`, `useAttachments`, `readFiles`/presigned URLs (**a bearer credential for the bytes** — never logged, reported, or persisted), workflow-generated files, **naming a zip's entries** (`{ id, name }` per file — a file name, never a path), preview pairing, filter operators, the server-side delivery bounds. **Uploads declare a `fidelity`** (`standard` / `high` / `original`) — the app picks how much of the image survives storage; use `high` whenever text must stay legible. |
|
package/dist/src/hooks.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ImageFidelity } from "./upload/optimize.js";
|
|
2
2
|
import { type AiContextValue } from "./rpc.js";
|
|
3
3
|
import { type AgentUIPart, type PendingChoice, type AgentRunLanding } from "./agent_stream.js";
|
|
4
|
-
import type { AppWorkflows, AppWorkflowResults, AppQueries, AppAgents, AppAgentResults } from "./types.js";
|
|
4
|
+
import type { AppWorkflows, AppWorkflowResults, AppQueries, AppQueryColumns, AppAgents, AppAgentResults } from "./types.js";
|
|
5
5
|
import type { ResolvedMember } from "./members.js";
|
|
6
6
|
import type { ResolvedOption } from "./select.js";
|
|
7
7
|
export type { AgentRunState, AgentUIPart, PendingChoice, ChoiceQuestion, ChoiceOption, AskUserChoiceOutput, AgentRunLanding } from "./agent_stream.js";
|
|
@@ -100,8 +100,11 @@ interface PaginatedQueryState<R> extends QueryStateBase {
|
|
|
100
100
|
* un-projected `field_key` is rejected), so an app can sort by any column it
|
|
101
101
|
* actually selects without the query template declaring it.
|
|
102
102
|
*/
|
|
103
|
-
export interface QuerySortKey {
|
|
104
|
-
|
|
103
|
+
export interface QuerySortKey<C extends string = string> {
|
|
104
|
+
/** One of the query's projected outputs — `AppQueryColumns[alias]` on a
|
|
105
|
+
* typed alias, so a column the query does not carry is a compile error
|
|
106
|
+
* rather than the server's request-time refusal. */
|
|
107
|
+
field_key: C;
|
|
105
108
|
order: "asc" | "desc";
|
|
106
109
|
/**
|
|
107
110
|
* Where rows BLANK in this column sit — `"bottom"` when omitted, which is what
|
|
@@ -116,10 +119,11 @@ export interface QuerySortKey {
|
|
|
116
119
|
*/
|
|
117
120
|
blank_position?: "top" | "bottom";
|
|
118
121
|
}
|
|
119
|
-
/** A filter condition over one output column (wire shape of a filter node).
|
|
120
|
-
|
|
122
|
+
/** A filter condition over one output column (wire shape of a filter node).
|
|
123
|
+
* `C` is the key's type — see `QuerySortKey`. */
|
|
124
|
+
export interface QueryFilterFieldCondition<C extends string = string> {
|
|
121
125
|
node_type: "condition";
|
|
122
|
-
field_key:
|
|
126
|
+
field_key: C;
|
|
123
127
|
type?: string;
|
|
124
128
|
operator: string;
|
|
125
129
|
value?: unknown;
|
|
@@ -135,21 +139,29 @@ export interface QueryFilterRecordIdCondition {
|
|
|
135
139
|
operator: string;
|
|
136
140
|
value?: unknown;
|
|
137
141
|
}
|
|
138
|
-
export type QueryFilterCondition = QueryFilterFieldCondition | QueryFilterRecordIdCondition;
|
|
142
|
+
export type QueryFilterCondition<C extends string = string> = QueryFilterFieldCondition<C> | QueryFilterRecordIdCondition;
|
|
139
143
|
/** A boolean group of filter nodes (wire shape — recursive). */
|
|
140
|
-
export interface QueryFilterGroup {
|
|
144
|
+
export interface QueryFilterGroup<C extends string = string> {
|
|
141
145
|
node_type: "group";
|
|
142
146
|
logic: "and" | "or";
|
|
143
|
-
children: Array<QueryFilterCondition | QueryFilterGroup
|
|
147
|
+
children: Array<QueryFilterCondition<C> | QueryFilterGroup<C>>;
|
|
144
148
|
}
|
|
145
149
|
/**
|
|
146
150
|
* Runtime filter applied AFTER the named query, bounded to its output columns
|
|
147
|
-
* (same exposure invariant as `sort`)
|
|
148
|
-
*
|
|
151
|
+
* (same exposure invariant as `sort`) — at compile time through `C`, and again
|
|
152
|
+
* on the server. Build a group from per-column filters with
|
|
153
|
+
* `columnFilterToConditions` (`@lotics/ui/column_filter`).
|
|
149
154
|
*/
|
|
150
|
-
export type QueryFilter = QueryFilterCondition | QueryFilterGroup
|
|
151
|
-
/**
|
|
152
|
-
|
|
155
|
+
export type QueryFilter<C extends string = string> = QueryFilterCondition<C> | QueryFilterGroup<C>;
|
|
156
|
+
/**
|
|
157
|
+
* The type a runtime `filter`/`sort` key takes on alias `K`: the union codegen
|
|
158
|
+
* wrote into `AppQueryColumns`, else `string` (the server's check is then the
|
|
159
|
+
* only one). A computed key must be narrowed to the union to compile.
|
|
160
|
+
*/
|
|
161
|
+
export type ColumnKeyOf<K extends string> = K extends keyof AppQueryColumns ? AppQueryColumns[K] & string : string;
|
|
162
|
+
/** Options shared by every query hook. `C` is the filter/sort key's type — see
|
|
163
|
+
* `ColumnKeyOf`. */
|
|
164
|
+
export interface BaseQueryOptions<C extends string = string> {
|
|
153
165
|
/**
|
|
154
166
|
* When `false`, the query does not run: `rows` stays empty, `loading` is
|
|
155
167
|
* false, and no request is sent. Flip it back to `true` to fetch. This is the
|
|
@@ -171,21 +183,21 @@ export interface BaseQueryOptions {
|
|
|
171
183
|
* Sort the result by output columns at runtime. Changing it re-queries (it is
|
|
172
184
|
* part of the cache key). Empty/omitted leaves the query's own order intact.
|
|
173
185
|
*/
|
|
174
|
-
sort?: QuerySortKey[];
|
|
186
|
+
sort?: QuerySortKey<C>[];
|
|
175
187
|
/**
|
|
176
188
|
* Filter the result by output columns at runtime. Changing it re-queries.
|
|
177
189
|
* Compose from per-column UI filters via `columnFilterToConditions`.
|
|
178
190
|
*/
|
|
179
|
-
filter?: QueryFilter
|
|
191
|
+
filter?: QueryFilter<C>;
|
|
180
192
|
}
|
|
181
193
|
/** Options for `useQuery` — a single fetch. */
|
|
182
|
-
export interface QueryOptions extends BaseQueryOptions {
|
|
194
|
+
export interface QueryOptions<C extends string = string> extends BaseQueryOptions<C> {
|
|
183
195
|
/** Max rows to fetch in the one request (a cap, not pagination). The server
|
|
184
196
|
* still clamps to its own maximum. Omit to fetch up to the server cap. */
|
|
185
197
|
pageSize?: number;
|
|
186
198
|
}
|
|
187
199
|
/** Options for `useInfiniteQuery` — append/load-more. */
|
|
188
|
-
export interface InfiniteQueryOptions extends BaseQueryOptions {
|
|
200
|
+
export interface InfiniteQueryOptions<C extends string = string> extends BaseQueryOptions<C> {
|
|
189
201
|
/** Rows per page. `loadMore()` appends the next page. */
|
|
190
202
|
pageSize: number;
|
|
191
203
|
}
|
|
@@ -196,14 +208,14 @@ export interface InfiniteQueryOptions extends BaseQueryOptions {
|
|
|
196
208
|
* COUNT over the filtered set; ordering it and paginating it are meaningless,
|
|
197
209
|
* and an option a hook silently drops is worse than one that will not compile.
|
|
198
210
|
*/
|
|
199
|
-
export type CountOptions = Omit<BaseQueryOptions
|
|
211
|
+
export type CountOptions<C extends string = string> = Omit<BaseQueryOptions<C>, "sort">;
|
|
200
212
|
/** Return value of `useCount` — the size of a filtered set, and nothing else. */
|
|
201
213
|
interface CountState extends QueryStateBase {
|
|
202
214
|
/** Rows in the filtered set. `undefined` until the count resolves. */
|
|
203
215
|
total: number | undefined;
|
|
204
216
|
}
|
|
205
217
|
/** Options for `usePaginatedQuery` — page-model with a total. */
|
|
206
|
-
export interface PaginatedQueryOptions extends BaseQueryOptions {
|
|
218
|
+
export interface PaginatedQueryOptions<C extends string = string> extends BaseQueryOptions<C> {
|
|
207
219
|
/** Rows per page. Default 25. */
|
|
208
220
|
pageSize?: number;
|
|
209
221
|
/**
|
|
@@ -289,7 +301,15 @@ export interface WorkflowResult<TData = unknown> {
|
|
|
289
301
|
files?: UploadedFile[];
|
|
290
302
|
data?: TData;
|
|
291
303
|
}
|
|
292
|
-
|
|
304
|
+
/**
|
|
305
|
+
* The (params, opts) tail of a query hook for alias `K`: `params` optional when
|
|
306
|
+
* the alias declares none, required otherwise, then an optional `opts` — a
|
|
307
|
+
* variadic tuple so `f("a", { … })` and `f("a", params, { … })` both type-check.
|
|
308
|
+
* ONE conditional signature, never a typed overload beside a loose
|
|
309
|
+
* `alias: string` one: a rejected filter key would fall through to the loose
|
|
310
|
+
* overload and compile.
|
|
311
|
+
*/
|
|
312
|
+
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];
|
|
293
313
|
/**
|
|
294
314
|
* Read rows from a query the app's author declared in `lotics.queries` — a
|
|
295
315
|
* single fetch, no pagination. For long lists use `usePaginatedQuery`
|
|
@@ -308,8 +328,7 @@ type QueryArgs<K extends keyof AppQueries & string, O> = AppQueries[K] extends R
|
|
|
308
328
|
* with the declared alias → param-type map, so an undeclared alias is a
|
|
309
329
|
* compile-time error and params are typed per the manifest.
|
|
310
330
|
*/
|
|
311
|
-
export declare function useQuery<K extends
|
|
312
|
-
export declare function useQuery(alias: string, params?: Record<string, unknown>, opts?: QueryOptions): QueryState<QueryRow>;
|
|
331
|
+
export declare function useQuery<K extends string>(alias: K, ...args: QueryArgs<K, QueryOptions<ColumnKeyOf<K>>>): QueryState<QueryRow>;
|
|
313
332
|
/** The resolved option set of one select column, plus an index for value
|
|
314
333
|
* rendering. The companion to a query row, for select fields. */
|
|
315
334
|
export interface FieldOptions {
|
|
@@ -381,8 +400,7 @@ export declare function useFieldOptions(alias: string, opts?: FieldOptionsOption
|
|
|
381
400
|
* const { rows, loadMore, hasMore } = useInfiniteQuery("feed", {}, { pageSize: 30 });
|
|
382
401
|
* ```
|
|
383
402
|
*/
|
|
384
|
-
export declare function useInfiniteQuery<K extends
|
|
385
|
-
export declare function useInfiniteQuery(alias: string, params?: Record<string, unknown>, opts?: InfiniteQueryOptions): InfiniteQueryState<QueryRow>;
|
|
403
|
+
export declare function useInfiniteQuery<K extends string>(alias: K, ...args: QueryArgs<K, InfiniteQueryOptions<ColumnKeyOf<K>>>): InfiniteQueryState<QueryRow>;
|
|
386
404
|
/**
|
|
387
405
|
* Page-model query with a total — the data hook behind a numbered, jumpable
|
|
388
406
|
* table (pairs with `@lotics/ui/pagination`). It owns the page
|
|
@@ -397,8 +415,7 @@ export declare function useInfiniteQuery(alias: string, params?: Record<string,
|
|
|
397
415
|
* usePaginatedQuery("orders", { q }, { pageSize: 25, sort, filter });
|
|
398
416
|
* ```
|
|
399
417
|
*/
|
|
400
|
-
export declare function usePaginatedQuery<K extends
|
|
401
|
-
export declare function usePaginatedQuery(alias: string, params?: Record<string, unknown>, opts?: PaginatedQueryOptions): PaginatedQueryState<QueryRow>;
|
|
418
|
+
export declare function usePaginatedQuery<K extends string>(alias: K, ...args: QueryArgs<K, PaginatedQueryOptions<ColumnKeyOf<K>>>): PaginatedQueryState<QueryRow>;
|
|
402
419
|
/**
|
|
403
420
|
* HOW MANY rows a query matches — one number, no rows fetched.
|
|
404
421
|
*
|
|
@@ -434,8 +451,7 @@ export declare function usePaginatedQuery(alias: string, params?: Record<string,
|
|
|
434
451
|
* return <Badge label={total == null ? "…" : `${total}`} />;
|
|
435
452
|
* ```
|
|
436
453
|
*/
|
|
437
|
-
export declare function useCount<K extends
|
|
438
|
-
export declare function useCount(alias: string, params?: Record<string, unknown>, opts?: CountOptions): CountState;
|
|
454
|
+
export declare function useCount<K extends string>(alias: K, ...args: QueryArgs<K, CountOptions<ColumnKeyOf<K>>>): CountState;
|
|
439
455
|
/** A file the host has stored and resolved serving URLs for. */
|
|
440
456
|
export interface UploadedFile {
|
|
441
457
|
id: string;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
export { mount } from "./mount.js";
|
|
18
18
|
export type { MountOptions } from "./mount.js";
|
|
19
19
|
export { useWorkflow, useQuery, useInfiniteQuery, usePaginatedQuery, useCount, useFieldOptions, useFileUpload, useAttachments, useMembers, useAgentRun, useAgentRuns, useAiContext, buildChoiceOutput, } from "./hooks.js";
|
|
20
|
-
export type { QueryRow, UploadedFile, AttachedFile, BaseQueryOptions, QueryOptions, InfiniteQueryOptions, PaginatedQueryOptions, CountOptions, QuerySortKey, QueryFilter, QueryFilterCondition, QueryFilterFieldCondition, QueryFilterRecordIdCondition, QueryFilterGroup, WorkflowResult, MembersOptions, AgentRunOptions, UseAgentRun, AgentRunLanding, AgentRunRecord, AgentRunState, AgentUIPart, PendingChoice, ChoiceQuestion, ChoiceOption, AskUserChoiceOutput, FieldOptions, FieldOptionsState, FieldOptionsOptions, } from "./hooks.js";
|
|
20
|
+
export type { QueryRow, UploadedFile, AttachedFile, BaseQueryOptions, QueryOptions, InfiniteQueryOptions, PaginatedQueryOptions, CountOptions, ColumnKeyOf, QuerySortKey, QueryFilter, QueryFilterCondition, QueryFilterFieldCondition, QueryFilterRecordIdCondition, QueryFilterGroup, WorkflowResult, MembersOptions, AgentRunOptions, UseAgentRun, AgentRunLanding, AgentRunRecord, AgentRunState, AgentUIPart, PendingChoice, ChoiceQuestion, ChoiceOption, AskUserChoiceOutput, FieldOptions, FieldOptionsState, FieldOptionsOptions, } from "./hooks.js";
|
|
21
21
|
export { useComments, useCommentCounts } from "./comments.js";
|
|
22
22
|
export type { AppComment, AppCommentFile, CommentsState, UseCommentsArgs, CommentCountsState, UseCommentCountsArgs, } from "./comments.js";
|
|
23
23
|
export { useViewer } from "./viewer.js";
|
|
@@ -33,7 +33,7 @@ export type { ResolvedMember } from "./members.js";
|
|
|
33
33
|
export { readSelect } from "./select.js";
|
|
34
34
|
export type { ResolvedOption } from "./select.js";
|
|
35
35
|
export type { AppFixture } from "./mock.js";
|
|
36
|
-
export type { AppWorkflows, AppWorkflowResults, AppQueries, AppAgents, AppAgentResults } from "./types.js";
|
|
36
|
+
export type { AppWorkflows, AppWorkflowResults, AppQueries, AppQueryColumns, AppAgents, AppAgentResults } from "./types.js";
|
|
37
37
|
export { row, readLinks, readFiles, readLocked } from "./row.js";
|
|
38
38
|
export type { ResolvedLink, AppFile } from "./row.js";
|
|
39
39
|
export { useOptimistic } from "./use_optimistic.js";
|
package/dist/src/types.d.ts
CHANGED
|
@@ -68,6 +68,26 @@ export interface AppWorkflowResults {
|
|
|
68
68
|
*/
|
|
69
69
|
export interface AppQueries {
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* The OUTPUT COLUMN NAMES each query projects — the same codegen, from the same
|
|
73
|
+
* manifest, as a literal union per alias:
|
|
74
|
+
*
|
|
75
|
+
* ```ts
|
|
76
|
+
* declare module "@lotics/app-sdk" {
|
|
77
|
+
* interface AppQueryColumns {
|
|
78
|
+
* "openOrders": "id" | "customer" | "total";
|
|
79
|
+
* }
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* A runtime `filter` / `sort` `field_key` is typed against it, so a column the
|
|
84
|
+
* query does not carry is a `tsc` error instead of the server's request-time
|
|
85
|
+
* refusal. An alias is ABSENT when its names cannot be read off the AST alone
|
|
86
|
+
* (a bare `from_table`); its key stays `string` and the server's check is the
|
|
87
|
+
* only one — a union that could be wrong is worse than none.
|
|
88
|
+
*/
|
|
89
|
+
export interface AppQueryColumns {
|
|
90
|
+
}
|
|
71
91
|
/**
|
|
72
92
|
* App-specific AGENT augmentation point — same pattern as `AppWorkflows`, for
|
|
73
93
|
* the streaming agents declared in `package.json` lotics.agents and invoked via
|
package/docs/data_fetching.md
CHANGED
|
@@ -91,21 +91,41 @@ Per-hook additions:
|
|
|
91
91
|
| `pageSize: number` | `useInfiniteQuery` | rows per appended page — the options type requires it, though omitting `opts` entirely type-checks and defaults to 30 |
|
|
92
92
|
| `pageSize?: number` | `usePaginatedQuery` | rows per page, default 25 |
|
|
93
93
|
|
|
94
|
-
Runtime `sort`/`filter` are **
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
Runtime `sort`/`filter` are **bounded to the named query's output columns**, twice over. At
|
|
95
|
+
compile time, `field_key` on a typed alias is the literal union of the columns the query projects —
|
|
96
|
+
codegen writes it into `.lotics/app_queries.d.ts` as `AppQueryColumns[alias]`, read off the query's
|
|
97
|
+
AST by the same naming rule the server applies — so a key the query does not carry fails
|
|
98
|
+
`npm run typecheck` (and therefore `lotics app check` and deploy) at your desk. At request time the
|
|
99
|
+
server checks the same rule again and rejects an un-projected key with an error, so a
|
|
100
|
+
sortable/filterable UI can never widen the app's data exposure even from an untyped call. The full
|
|
101
|
+
runtime-refinement contract (which operators are valid per column type, record-link membership
|
|
102
|
+
filtering, and the field-less system conditions — `record_id` works in a runtime filter; `locked` /
|
|
103
|
+
`current_member` are template-only and rejected at the runtime layer) lives in
|
|
104
|
+
[./queries.md](./queries.md).
|
|
105
|
+
|
|
106
|
+
**A key you compute must be narrowed, not widened.** A register that derives its filter columns
|
|
107
|
+
from a ladder — `` `hs_${step.leaves}` `` — must type `leaves` as the literal union of the stamps
|
|
108
|
+
it can name, so the template literal resolves to members of `AppQueryColumns[alias]`; a `string`
|
|
109
|
+
there is a compile error on a typed alias, and that error is the point. It is exactly the bug the
|
|
110
|
+
type exists to catch: a rung added to the ladder without its column added to the query, which no
|
|
111
|
+
`app dev` session notices until a member scopes to the one project on that ladder. An alias whose
|
|
112
|
+
columns cannot be known from its AST (a bare `from_table`) has no `AppQueryColumns` entry, and its
|
|
113
|
+
key stays `string` — the server's check is then the only one. A helper that builds a filter or
|
|
114
|
+
sort for a typed alias names its keys `ColumnKeyOf<"alias">` — `QueryFilter<ColumnKeyOf<"register">>`,
|
|
115
|
+
`QuerySortKey<…>`; `QueryFilterCondition` and `QueryFilterGroup` take the same parameter — so the
|
|
116
|
+
check reaches the helper that derives the key, not only the hook call that sends it.
|
|
117
|
+
|
|
118
|
+
`@lotics/ui`'s `columnFilterToConditions` carries the same parameter (`FilterableColumn<C>` →
|
|
119
|
+
`FilterConditionNode<C>`), so a per-column filter UI built over a typed alias composes without a
|
|
120
|
+
cast.
|
|
100
121
|
|
|
101
122
|
Refinement order is fixed: the runtime **filter narrows** the named query's result, then **sort
|
|
102
123
|
orders** it, then **limit/offset paginate** it. The template's own filters/sort/limit run first,
|
|
103
124
|
inside the named query.
|
|
104
125
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
server validates system conditions by `type` and never reads `field_key` on them.
|
|
126
|
+
The field-less `record_id` system condition documented in [./queries.md](./queries.md) (the one
|
|
127
|
+
usable in a runtime filter) is its own member of `QueryFilterCondition` with no `field_key` at all —
|
|
128
|
+
write it as `{ node_type: "condition", type: "record_id", operator, value }`.
|
|
109
129
|
|
|
110
130
|
## Caching, loading states, and errors
|
|
111
131
|
|
package/docs/files.md
CHANGED
|
@@ -99,7 +99,7 @@ to object storage → finalize. The API server never proxies the bytes.
|
|
|
99
99
|
|
|
100
100
|
| Bound | Value |
|
|
101
101
|
|---|---|
|
|
102
|
-
| Max file size |
|
|
102
|
+
| Max file size | 2 GiB (2,147,483,648 bytes) — enforced at URL mint *and* re-verified at finalize; a video passes through untouched, so this is the bound that matters for one |
|
|
103
103
|
| Allowed MIME types | **any** — type is unrestricted at upload |
|
|
104
104
|
| Upload-URL validity | 10 minutes |
|
|
105
105
|
|