@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 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. |
@@ -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
- field_key: string;
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
- export interface QueryFilterFieldCondition {
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: string;
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`). Build a group from per-column filters
148
- * with `columnFilterToConditions` (`@lotics/ui/column_filter`).
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
- /** Options shared by every query hook. */
152
- export interface BaseQueryOptions {
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, "sort">;
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
- type QueryArgs<K extends keyof AppQueries & string, O> = AppQueries[K] extends Record<string, never> ? [params?: Record<string, never>, opts?: O] : [params: AppQueries[K], opts?: O];
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 keyof AppQueries & string>(alias: K, ...args: QueryArgs<K, QueryOptions>): QueryState<QueryRow>;
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 keyof AppQueries & string>(alias: K, ...args: QueryArgs<K, InfiniteQueryOptions>): InfiniteQueryState<QueryRow>;
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 keyof AppQueries & string>(alias: K, ...args: QueryArgs<K, PaginatedQueryOptions>): PaginatedQueryState<QueryRow>;
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 keyof AppQueries & string>(alias: K, ...args: QueryArgs<K, CountOptions>): CountState;
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;
@@ -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";
@@ -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
@@ -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 **server-bounded to the named query's output columns** a `field_key`
95
- the query doesn't project is rejected with an error, so a sortable/filterable UI can never widen
96
- the app's data exposure. The full runtime-refinement contract (which operators are valid per column
97
- type, record-link membership filtering, and the field-less system conditions `record_id` works in
98
- a runtime filter; `locked` / `current_member` are template-only and rejected at the runtime layer)
99
- lives in [./queries.md](./queries.md).
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 appliesso 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
- **Limitation:** the exported `QueryFilterCondition` type declares `field_key` as required, but the
106
- field-less `record_id` system condition documented in [./queries.md](./queries.md) (the one usable
107
- in a runtime filter) carries no field. To satisfy the type, include `field_key: ""` on it — the
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 | 25 MiB (26,214,400 bytes) — enforced at URL mint *and* re-verified at finalize |
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/app-sdk",
3
- "version": "0.87.9",
3
+ "version": "0.88.0",
4
4
  "description": "Runtime SDK for Lotics custom-code apps \u2014 typed hooks, postMessage bridge, mount entry point",
5
5
  "type": "module",
6
6
  "exports": {