@lotics/app-sdk 0.59.0 → 0.59.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 +1 -1
- package/docs/mutations.md +56 -0
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -16,7 +16,7 @@ signature; open the file.**
|
|
|
16
16
|
|---|---|
|
|
17
17
|
| [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. |
|
|
18
18
|
| [docs/data_fetching.md](./docs/data_fetching.md) | The three read hooks (`useQuery`/`useInfiniteQuery`/`usePaginatedQuery`), cell readers (`row.*`, `readSelect`, `readMembers`, `readLinks`, `readFiles`, `readLocked`), `useFieldOptions`, data discipline, the search-as-you-type + record-picker patterns. |
|
|
19
|
-
| [docs/mutations.md](./docs/mutations.md) | `useWorkflow` (the ONLY write path), the `WorkflowResult` resolve-never-throw contract, typed inputs, diff-before-update, locked records, `useOptimistic
|
|
19
|
+
| [docs/mutations.md](./docs/mutations.md) | `useWorkflow` (the ONLY write path), the `WorkflowResult` resolve-never-throw contract, typed inputs, diff-before-update, locked records, `useOptimistic`, read-after-write ordering (a re-read must not overtake an in-flight write). |
|
|
20
20
|
| [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 `lotics app workflow check` loop. |
|
|
21
21
|
| [docs/files.md](./docs/files.md) | Files end to end — `useFileUpload`, `useAttachments`, `readFiles`/presigned URLs, workflow-generated files, 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. |
|
|
22
22
|
| [docs/members_and_options.md](./docs/members_and_options.md) | People + select options + comments — `useMembers`, `useFieldOptions`, `useViewer`, `useComments`, and the `@lotics/ui` components they feed. |
|
package/docs/mutations.md
CHANGED
|
@@ -319,6 +319,62 @@ flash to a spinner — `loading` stays false during revalidation). `usePaginated
|
|
|
319
319
|
(`revalidateOnFocus`, default on) eventually self-corrects stale data, but never rely on it
|
|
320
320
|
in place of an explicit refetch after a write the user is watching for.
|
|
321
321
|
|
|
322
|
+
### A read must not overtake an in-flight write
|
|
323
|
+
|
|
324
|
+
`refetch()` and a direct re-read know nothing about a write that is still running. On a record
|
|
325
|
+
surface with inline (blur-committing) fields, a single gesture starts both: the field's write on
|
|
326
|
+
mousedown, the button's handler on mouseup — so a handler that re-reads the record to build a
|
|
327
|
+
document, mint something, or copy the row can read the row as it was BEFORE the edit. It fails
|
|
328
|
+
silently: the screen shows the new value, the output carries the old one.
|
|
329
|
+
|
|
330
|
+
Serialize them with one barrier per record surface — every write chains on, and the ONE place
|
|
331
|
+
that re-reads stored state awaits it, so no handler can forget:
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
// Per surface, not module-level: useMemo(createWriteBarrier, []) inside the hook.
|
|
335
|
+
export function createWriteBarrier() {
|
|
336
|
+
let chain: Promise<unknown> = Promise.resolve();
|
|
337
|
+
return {
|
|
338
|
+
// Returns the write UNCHANGED — the caller keeps its own result + error handling;
|
|
339
|
+
// the chain never rejects, it only tracks when writes SETTLE.
|
|
340
|
+
track: <T,>(write: Promise<T>): Promise<T> => {
|
|
341
|
+
chain = chain.then(() => write).catch(() => undefined);
|
|
342
|
+
return write;
|
|
343
|
+
},
|
|
344
|
+
settled: () => chain,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const saveField = async (key: string, value: unknown) => {
|
|
349
|
+
const r = await writes.track(saveRecord({ record_id, [key]: value }));
|
|
350
|
+
if (r.status === "error") throw new Error(r.message); // the inline editor keeps the edit
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
/** The ONLY re-read of stored state. Every handler goes through it — never the cached row. */
|
|
354
|
+
const reload = async () => {
|
|
355
|
+
await writes.settled();
|
|
356
|
+
const res = await rpc<{ rows?: Rec[] }>("query", { alias: "record", params: { id: record_id } });
|
|
357
|
+
return res.rows?.[0] ?? null;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const printDoc = async () => {
|
|
361
|
+
const fresh = (await reload()) ?? current; // reflects the edit the press just committed
|
|
362
|
+
await issueDoc(buildDoc(fresh));
|
|
363
|
+
};
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
A handler that reads the row held in state instead has the same defect with no in-flight write
|
|
367
|
+
needed — any edit since the row was loaded is missing. Build documents and mints from the
|
|
368
|
+
re-read, not from the cached row.
|
|
369
|
+
|
|
370
|
+
The same trap one level up: **an action takes an ID, never a captured object.**
|
|
371
|
+
`onPress={() => issueInvoice(invoice)}` closes over the invoice as it was when the press
|
|
372
|
+
happened, so no amount of waiting refreshes it — the confirm quotes, and the mint bills, the
|
|
373
|
+
pre-edit total. Pass the key and resolve at use (`onPress={() => setConfirmKey(invoice.key)}`,
|
|
374
|
+
then re-read or `find` where it is consumed). `@lotics/ui` gates the press itself
|
|
375
|
+
(`docs/data_entry.md` § Inline edit), which fixes the ordering; the captured value is the app's
|
|
376
|
+
to get right.
|
|
377
|
+
|
|
322
378
|
## Diff before update — send only what changed
|
|
323
379
|
|
|
324
380
|
An edit form snapshots the record's values when it loads, and on save sends **only the fields
|