@lotics/app-sdk 0.124.6 → 0.124.8

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.
@@ -1,6 +1,6 @@
1
1
  /** Upload: optimize (images) → presigned URL → PUT with retry → complete. */
2
2
  import { type ImageFidelity } from "./optimize.js";
3
- import type { PhotoPlace } from "../row.js";
3
+ import type { PhotoPlace } from "../shared_types.js";
4
4
  interface UploadInitResponse {
5
5
  upload_url: string;
6
6
  file_id: string;
@@ -25,6 +25,7 @@ untyped. It is written from the app's live bindings by `lotics app create --cust
25
25
  | `useQueries(calls, opts?)` | one state per `{ alias, params?, filter?, sort?, aggregate?, total?, rows? }`, in order — a `sort` orders that call's rows before the cap cuts them | reads that are DATA — a list known only at render, one read per item, or one count per stage (`rows: false, total: true`, each state's `.total`); the deploy's alias scan reads a list as dynamic, so every alias in it is still declared. With `aggregate` a call answers the groups of its filtered rows instead of the rows ([queries](./queries.md)) |
26
26
  | `queryAll(alias, params?, { filter, sort })` | a promise of every row | outside React, for a job that must hold the whole narrowed set in the browser (the ids an act runs on): it asks page after page until the server says the set ended, never stopping at the 10,000-row cap. The server's rows as stored — no write of this app is drawn over them |
27
27
  | `exportQuery(alias, { params, filter, sort, tabs?, report, file, labels? })` | a promise of an `ExportedFile`: `{ filename, mime_type, url, size, created_at }` | every row the narrowed set holds, made into one file by the server — no row reaches the browser. `tabs` (2 to 5, each `{ tab, label, filter?, sort?, totals? }`; `totals` is `{ query, params, via, columns }`, the read of a totals tab beside its rows, joined on the link column `via`, refused where that read lacks `via` or a column or answers two groups for one row) are each read as the export's own rows are — same query and params, under the tab's own filter and sort (absent, the declared order) — and a template reads each one's rows under its `tab`, a name none of the report's keys nor the template's `rows` key holds. `report` is `{ title, lines, readings, dates?, period?, per?, per_row?, filename? }` — `dates` each date filter's `{ from?, to? }` by field alias, each bound a day (`yyyy-MM-dd`) or a minute of one (`yyyy-MM-ddTHH:mm`) with no zone; `period` the tabs' `{ from, to }`, bounds of the same form; `per` the value the report was picked for, in the reader's words; `per_row` the picked row's fields by field alias, each a text, number, boolean or `null`, which `filename` and a template read as `per_row.<field>`; `filename` the file's name in the template grammar over one value each — `title`, `at` (the moment the server made the file), `dates.<field>.from` or `.to`, `period.from` or `.to`, `per`, `per_row.<field>`, `lines | lookup:<n>`, with `| add_days:<n>` shifting a date by whole days — at most 200 bytes once filled (absent, the template's name, or `title`); `file` is `{ kind: "workbook", language, sheet, columns }` (the default report workbook, its head closing with the moment the server made it, each column `{ key, header, type }` an output column of the query — a `number` one on a number column, a `date` one on a date or datetime; with `tabs`, one sheet per tab named by its `label` in place of `sheet`) or `{ kind: "template", template }`, one the query declares under `templates` by name ([queries](./queries.md)). `labels`, by `select` output column, the words each option key prints as in either file — a cell no field of the query names (a union's literal column) arrives as its bare key; a cell its field's option names prints as that, a key the map lacks as itself, and a column no select of the query holds as it is. Past 20,000 rows, in any tab, it rejects, never cuts; each caller makes at most 60 exports a minute. The file has no `id` — it is a download, not a workspace file a workflow or a field takes: its `url` is signed for the caller for at most an hour, so download it with `downloadFileFromUrl(url, filename)` (`@lotics/ui/download`) when the promise resolves — a tab `openExternal` opens seconds after the click is a pop-up the browser blocks; the server deletes the file a day after it is made |
28
+ | `exportApp(key, narrowing)` | a promise of an `ExportedFile`, as `exportQuery`'s | the file the server makes of an Export `key` of a JSON app's tree (a custom-code app has no Export and makes its files with `exportQuery`) over the register as `narrowing` states it (`tab`, `status`, `filters`, `search`, `overdue`, `waiting`, `period`, `window`, `sort`, `group`, `folder`, `per`; a `filters` entry `null` clears the chip the register opens on): the server reads the rows, the band's readings and the names under the caller's own access. Download it as `exportQuery`'s |
28
29
 
29
30
  Every hook answers the same `QueryState`: `rows`, `truncated`, `total`, `counts`, `loading`,
30
31
  `isValidating`, `answeredAt`, `error`, `pending`, `refetch`, `page`, `setPage`, `pageCount`, `hasMore`,
@@ -144,7 +145,8 @@ write it as `{ node_type: "condition", type: "record_id", operator, value }`.
144
145
  inline `{ status: "open" }` each render is the same key; you never need to memoize params.
145
146
  - **The cache survives unmount/remount**, and **arrival is a refresh event**: returning to a screen
146
147
  renders the cached rows instantly *and* re-reads them in the background, so a list reflects what
147
- another screen changed while you were away. Identical concurrent reads share one request, and reads asked within a few milliseconds of each other go to the server together, each answered or refused on its own.
148
+ another screen changed while you were away. Arrival re-reads a read that answered, not one whose
149
+ last ask failed. Identical concurrent reads share one request, and reads asked within a few milliseconds of each other go to the server together, each answered or refused on its own.
148
150
  Freshness comes from four places — arrival, window focus / tab return / network reconnect
149
151
  (`revalidateOnFocus`, default on; a read asked within the last minute is not asked again,
150
152
  and the reads it asks again re-render together, as a push's do — so what changed while the tab
@@ -188,7 +190,8 @@ write it as `{ node_type: "condition", type: "record_id", operator, value }`.
188
190
  where `answeredAt` moved and the call is the same.
189
191
  - **`error`** is a `string | null`. A failed query surfaces immediately — there is **no automatic
190
192
  retry** (no retry loop that masks the error). The last successful rows for the same key stay
191
- rendered. The next focus re-read or an explicit `refetch()` re-runs it.
193
+ rendered. It stays failed until a realtime push or this app's own write names it, a focus or
194
+ reconnect re-read, or an explicit `refetch()`; mounting the screen again does not re-run it.
192
195
  - **Never derive a FIGURE from `rows` without gating on `error`.** A first read that fails leaves
193
196
  `rows` at `[]`, and `[]` is indistinguishable from a genuinely empty result — so `rows.reduce(…)`
194
197
  returns `0`, `rows.length` returns `0`, and a screen states a confident number that no query
package/docs/files.md CHANGED
@@ -240,7 +240,7 @@ A caller outside the app's organization receives only `id`, `filename`, `mime_ty
240
240
  `thumbnail_url`, `preview_url`, `size`, `created_at` and `document_template_id` — never the storage
241
241
  address, and never a place.
242
242
 
243
- Decode with `readFiles(cell)` → `AppFile[]` (`dist/row.d.ts`):
243
+ Decode with `readFiles(cell)` → `AppFile[]` (`dist/shared_types.d.ts`):
244
244
 
245
245
  | Field | Type | Notes |
246
246
  |---|---|---|
package/docs/runtime.md CHANGED
@@ -68,7 +68,7 @@ Activation is a **two-step gate** — both must hold, so demo data shipping in t
68
68
  bundle never leaks into normal traffic:
69
69
 
70
70
  1. A fixture is registered via `mount({ fixture })` (`AppFixture` type:
71
- `dist/mock.d.ts` — `{ queries?, workflows?, agents?, exports?, recordings? }`).
71
+ `dist/mock.d.ts` — `{ queries?, workflows?, agents?, exports?, exportApps?, recordings? }`).
72
72
  2. The page URL carries `?__mock=1` (exactly `1`). Without the flag the fixture
73
73
  is completely inert.
74
74
 
@@ -79,7 +79,7 @@ the fixture nothing, as it asks the server nothing. **Partial mocking is
79
79
  supported**: an alias absent from the fixture still flows through the real
80
80
  transport. `fixture.exports[alias]` answers `exportQuery(alias, call)` — a
81
81
  function of the call returning an `ExportedFile`, or a promise of one to review the
82
- pending state. Calling `mount` again (HMR) replaces the registration last-write-wins.
82
+ pending state; `fixture.exportApps[key]` answers `exportApp(key, narrowing)` the same way. Calling `mount` again (HMR) replaces the registration last-write-wins.
83
83
 
84
84
  - **Fixture rows stand in for wire rows.** They pass through the same code path
85
85
  as fetched rows, so shape them exactly like the query's real output — the same
@@ -258,7 +258,8 @@ semantics are documented:
258
258
  | Op | Payload | Resolves to | Owning doc |
259
259
  |---|---|---|---|
260
260
  | `query` | `{ alias, params?, limit?, offset?, … }` | `{ rows }` | [queries](./queries.md), [data fetching](./data_fetching.md) |
261
- | `export` | `{ alias, params?, filter?, sort?, report, file }` | `{ file }` | [data fetching](./data_fetching.md) |
261
+ | `export` | `{ alias, params?, filter?, sort?, report, file }` (`exportQuery`) or `{ export: { key, … } }` (`exportApp`) | `{ file }` | [data fetching](./data_fetching.md) |
262
+ | `spec` | `{}` | `HostSpec`: the app's compiled `app.json` itself (format 29: `lists`, `pages` by entity, `entities`, flat `acts` and `checks`; a dashboard's readings), never the route's wrapper | this doc |
262
263
  | `field_options` | `{ alias }` | `{ fields }` | [members & options](./members_and_options.md) |
263
264
  | `workflow` | `{ alias, inputs }` | `WorkflowResult` | [mutations](./mutations.md) |
264
265
  | `upload` | `{ file: File }` (the `File` crosses the bridge by structured clone) | uploaded-file object | [files](./files.md) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/app-sdk",
3
- "version": "0.124.6",
3
+ "version": "0.124.8",
4
4
  "description": "The SDK a Lotics custom-code app reads and writes through — typed hooks over the host bridge, cell readers, mount() and AppRouter",
5
5
  "type": "module",
6
6
  "exports": {