@lotics/app-sdk 0.100.0 → 0.101.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.
Files changed (108) hide show
  1. package/AGENTS.md +32 -47
  2. package/dist/agent_stream.d.ts +131 -0
  3. package/dist/ask_ai.d.ts +27 -0
  4. package/dist/attachments.d.ts +58 -0
  5. package/dist/chunk-ARV5FAU5.js +1132 -0
  6. package/dist/comments.d.ts +89 -0
  7. package/dist/error_report.d.ts +9 -0
  8. package/dist/folder_pick.d.ts +8 -0
  9. package/dist/geolocation.d.ts +42 -0
  10. package/dist/hooks.d.ts +251 -0
  11. package/dist/{src/index.d.ts → index.d.ts} +13 -22
  12. package/dist/index.js +31309 -0
  13. package/dist/index.js.LEGAL.txt +11 -0
  14. package/dist/members.d.ts +32 -0
  15. package/dist/mock.d.ts +37 -0
  16. package/dist/mount.d.ts +19 -0
  17. package/dist/new_record.d.ts +37 -0
  18. package/dist/open_app.d.ts +12 -0
  19. package/dist/open_external.d.ts +10 -0
  20. package/dist/overlay.d.ts +25 -0
  21. package/dist/queries.d.ts +231 -0
  22. package/dist/recording.d.ts +47 -0
  23. package/dist/recording_state.d.ts +43 -0
  24. package/dist/rename_file.d.ts +13 -0
  25. package/dist/router.d.ts +10 -0
  26. package/dist/router.js +97 -0
  27. package/dist/row.d.ts +87 -0
  28. package/dist/rpc.d.ts +114 -0
  29. package/dist/select.d.ts +24 -0
  30. package/dist/shared_types.d.ts +8 -0
  31. package/dist/store.d.ts +43 -0
  32. package/dist/types.d.ts +36 -0
  33. package/dist/upload/optimize.d.ts +30 -0
  34. package/dist/upload/pipeline.d.ts +36 -0
  35. package/dist/upload/transport.d.ts +19 -0
  36. package/dist/url_params.d.ts +55 -0
  37. package/dist/use_recents.d.ts +15 -0
  38. package/dist/{src/use_url_state.d.ts → use_url_state.d.ts} +0 -2
  39. package/dist/viewer.d.ts +41 -0
  40. package/dist/written.d.ts +77 -0
  41. package/docs/ai.md +74 -133
  42. package/docs/data_fetching.md +209 -290
  43. package/docs/files.md +61 -51
  44. package/docs/members_and_options.md +93 -63
  45. package/docs/mutations.md +135 -205
  46. package/docs/navigation_and_state.md +26 -35
  47. package/docs/queries.md +144 -207
  48. package/docs/recipes.md +21 -45
  49. package/docs/runtime.md +74 -137
  50. package/docs/security.md +8 -11
  51. package/docs/workflows.md +189 -174
  52. package/package.json +27 -28
  53. package/dist/src/agent_stream.d.ts +0 -200
  54. package/dist/src/agent_stream.js +0 -314
  55. package/dist/src/ask_ai.d.ts +0 -40
  56. package/dist/src/ask_ai.js +0 -35
  57. package/dist/src/attachments.d.ts +0 -68
  58. package/dist/src/attachments.js +0 -93
  59. package/dist/src/comments.d.ts +0 -127
  60. package/dist/src/comments.js +0 -192
  61. package/dist/src/download.js +0 -54
  62. package/dist/src/geolocation.d.ts +0 -64
  63. package/dist/src/geolocation.js +0 -96
  64. package/dist/src/hooks.d.ts +0 -781
  65. package/dist/src/hooks.js +0 -860
  66. package/dist/src/index.js +0 -34
  67. package/dist/src/members.d.ts +0 -105
  68. package/dist/src/members.js +0 -62
  69. package/dist/src/mock.d.ts +0 -118
  70. package/dist/src/mock.js +0 -124
  71. package/dist/src/mount.d.ts +0 -47
  72. package/dist/src/mount.js +0 -34
  73. package/dist/src/new_record.d.ts +0 -74
  74. package/dist/src/new_record.js +0 -117
  75. package/dist/src/open_app.d.ts +0 -15
  76. package/dist/src/open_app.js +0 -18
  77. package/dist/src/open_external.d.ts +0 -16
  78. package/dist/src/open_external.js +0 -19
  79. package/dist/src/recording.d.ts +0 -59
  80. package/dist/src/recording.js +0 -30
  81. package/dist/src/recording_state.d.ts +0 -59
  82. package/dist/src/recording_state.js +0 -94
  83. package/dist/src/router.d.ts +0 -17
  84. package/dist/src/router.js +0 -144
  85. package/dist/src/row.d.ts +0 -159
  86. package/dist/src/row.js +0 -254
  87. package/dist/src/rpc.d.ts +0 -207
  88. package/dist/src/rpc.js +0 -904
  89. package/dist/src/select.d.ts +0 -34
  90. package/dist/src/select.js +0 -40
  91. package/dist/src/types.d.ts +0 -115
  92. package/dist/src/types.js +0 -1
  93. package/dist/src/upload/optimize.d.ts +0 -54
  94. package/dist/src/upload/optimize.js +0 -207
  95. package/dist/src/upload/pipeline.d.ts +0 -55
  96. package/dist/src/upload/pipeline.js +0 -52
  97. package/dist/src/upload/transport.d.ts +0 -42
  98. package/dist/src/upload/transport.js +0 -128
  99. package/dist/src/url_params.d.ts +0 -93
  100. package/dist/src/url_params.js +0 -215
  101. package/dist/src/use_optimistic.d.ts +0 -27
  102. package/dist/src/use_optimistic.js +0 -27
  103. package/dist/src/use_recents.d.ts +0 -19
  104. package/dist/src/use_recents.js +0 -71
  105. package/dist/src/use_url_state.js +0 -73
  106. package/dist/src/viewer.d.ts +0 -26
  107. package/dist/src/viewer.js +0 -47
  108. /package/dist/{src/download.d.ts → download.d.ts} +0 -0
package/dist/src/row.d.ts DELETED
@@ -1,159 +0,0 @@
1
- /**
2
- * Accessors that coerce raw `useQuery` row values into typed values. Each cell
3
- * of a query row is `unknown`: the query layer serializes a select column as a
4
- * `{ key, label }` array (one entry per selected option), a date/datetime as a
5
- * `YYYY-MM-DD[THH:mm…]` string, a number/boolean as itself.
6
- *
7
- * These live in app-sdk — next to `useQuery`, whose serialization contract they
8
- * decode — so a change to that contract updates them in one place, and so apps
9
- * stop re-implementing the same coercions. They are pure (`unknown` in, value
10
- * out) and never throw.
11
- */
12
- /**
13
- * Select → first option key. Reads the enriched `{ key, label }` shape (and the
14
- * legacy bare id / single-element array / `{ id }` shapes for back-compat).
15
- * Use `readSelect` for the full `{ key, label }[]` on multi-select cells.
16
- */
17
- declare function opt(v: unknown): string | null;
18
- /** Text/markdown/autonumber → string (numbers stringified; everything else ""). */
19
- declare function text(v: unknown): string;
20
- /**
21
- * Number → number, or `null` when the cell holds no number — an empty cell, a
22
- * NaN/Infinity, an unparseable string. Empty and zero usually mean opposite
23
- * things ("not priced yet" vs "free", "no reading" vs "a reading of 0"), so the
24
- * absence is preserved the way `date` preserves it. Write `?? 0` where 0 IS the
25
- * correct reading of an empty cell (a sum, a count).
26
- */
27
- declare function num(v: unknown): number | null;
28
- /**
29
- * Checkbox/boolean → `true` / `false`, or `null` when the cell holds neither —
30
- * an empty cell, a value of another kind. (The strings "true"/"false" decode.)
31
- *
32
- * AN UNANSWERED CHECKBOX IS NOT A "NO", exactly as an empty number cell is not a
33
- * 0: the field's default is what the workspace writes when a row states
34
- * something, and a row nobody has answered states nothing. Read as `false` it
35
- * drew "No" for every partner nobody had assessed — the rows a gate exists to
36
- * catch are precisely the ones that answer nothing — so the absence is preserved
37
- * the way `num` and `date` preserve theirs. Write `=== true` where only the
38
- * affirmative acts, `?? false` where the safe side IS the reading.
39
- */
40
- declare function bool(v: unknown): boolean | null;
41
- /**
42
- * Date/datetime field → a LOCAL-midnight Date for the stored calendar day, so
43
- * calendar/gantt placement never shifts across timezones. Parses the leading
44
- * `YYYY[-MM[-DD]]` of the serialized string — a reduced-precision `date` value
45
- * ("2026-05" / "2026") decodes to its PERIOD START (missing month/day → 1)
46
- * rather than vanishing to null. Null only if absent or unparseable. (Range
47
- * fields are not handled here — they have no consumer yet.)
48
- */
49
- declare function date(v: unknown): Date | null;
50
- /**
51
- * Date/datetime field → a LOCAL Date that KEEPS the stored wall-clock time, so
52
- * `getHours()` / `toLocaleTimeString()` render the time as written. The stored
53
- * value is a timezone-less workspace wall-clock (see the serialization note
54
- * above), so it is read verbatim — no UTC conversion. Use this when the time
55
- * matters (a check-in time, an appointment); `date` keeps only the calendar day.
56
- * Parses `YYYY[-MM[-DD]]` with an optional `T`-or-space `HH:mm[:ss]`; a missing
57
- * month/day is the period start (1) and a missing time is midnight — a
58
- * reduced-precision value decodes rather than vanishing. Null if absent or
59
- * unparseable.
60
- */
61
- declare function datetime(v: unknown): Date | null;
62
- /** A linked record cell entry — the target record's id + its display text. */
63
- export interface ResolvedLink {
64
- /** The linked record's id (e.g. "rec_…"). Use to correlate/filter. */
65
- id: string;
66
- /** The linked record's display text (its primary-field value). Use to render. */
67
- display: string;
68
- }
69
- /**
70
- * select_record_link → the FIRST linked record as `{ id, display }` (or null).
71
- * The common single-link case: read `.display` to render the linked record's
72
- * name, `.id` to correlate/filter (e.g. group rows by a parent record). Use
73
- * `readLinks` for the full list on multi-link fields.
74
- */
75
- declare function link(v: unknown): ResolvedLink | null;
76
- /**
77
- * select_record_link → ALL linked records as `{ id, display }[]` (empty if none).
78
- *
79
- * A LOOKUP OF A LINK FIELD ARRIVES ONE LEVEL DEEPER — the cell holds one entry
80
- * per linked record and each entry is THAT record's whole link cell, an array —
81
- * so read entry-by-entry it decoded to nothing while the value was plainly
82
- * there. The nesting is flattened here, at the boundary that owns the
83
- * serialization, rather than at each caller: one level, in order, and a record
84
- * two linked rows both point at is one link, so ids repeat at most once.
85
- * Anything else still reads as empty.
86
- */
87
- export declare function readLinks(v: unknown): ResolvedLink[];
88
- /**
89
- * A `files`-field cell entry, as the app query serializes it. The server
90
- * presigns each file (24h) so `url`/`thumbnail_url` load directly — including
91
- * from the sandboxed app iframe and for public apps — so the app can preview or
92
- * download without deriving its own URL.
93
- */
94
- export interface AppFile {
95
- id: string;
96
- filename: string;
97
- mime_type: string;
98
- /** Presigned serving URL — render in an <Image>/preview or pass to openExternal. */
99
- url: string;
100
- /** Presigned thumbnail URL for images, when the server produced one. */
101
- thumbnail_url?: string;
102
- /** Byte size of the file, resolved from the file object at serving time. Absent
103
- * for older files not yet backfilled — show a size only when present. Prefer a
104
- * dedicated sortable `Table` column over the RAW number (the `tpl_record`
105
- * documents register: a right-aligned "Size" column, formatted at display) —
106
- * not a crammed `FileRow` meta string, which sorts wrong ("8.4 MB" < "96 KB"). */
107
- size?: number | null;
108
- /** ISO upload timestamp (date-added), resolved at serving time. Show as a
109
- * dedicated "Added" `Table` column (format with `formatDate`), sortable over the
110
- * raw ISO value. */
111
- created_at?: string;
112
- }
113
- /**
114
- * files field → the attached files with their presigned `url` (empty if none).
115
- * Skips entries the server didn't presign (no `url`) so a consumer never renders
116
- * an unservable file. Pass one to `toDisplayFile` from `@lotics/ui/file_thumbnail`
117
- * for FileThumbnail/Gallery — the kit owns that conversion, so don't re-declare
118
- * it per app.
119
- *
120
- * A LOOKUP OF A FILES FIELD ARRIVES ONE LEVEL DEEPER, exactly as a lookup of a
121
- * link field does (see {@link readLinks}) — the cell holds one entry per linked
122
- * record and each entry is THAT record's whole files cell, an array — so read
123
- * entry-by-entry it decoded to nothing while the pictures were plainly there,
124
- * and every mark drawn off a looked-up photograph fell back to its glyph. The
125
- * nesting is flattened here, at the boundary that owns the serialization, rather
126
- * than at each caller: one level, in the order the rows link, and a file two
127
- * linked rows both carry is one file, so ids repeat at most once.
128
- */
129
- export declare function readFiles(v: unknown): AppFile[];
130
- /**
131
- * Record lock state for a `useQuery` row. The query layer emits a row-level
132
- * `__source_locked` addressing column (alongside `__source_record_id`). A locked
133
- * record rejects direct writes, so an app reads this to show a locked state and
134
- * route edits through a `request_locked_field_change` workflow instead of an
135
- * `update_records` save. Pass the whole row (not a cell). False for any
136
- * non-object / absent flag.
137
- */
138
- export declare function readLocked(rowValue: unknown): boolean;
139
- /**
140
- * When the record behind this row was created — the row-level `__created_at`
141
- * column the compiler emits on every row-level query result. It is a DISPLAY
142
- * value, so a table with no date field of its own still has a chronological
143
- * anchor (a kardex date, an "as of" caption) with no schema change. Absent on a
144
- * grouped/aggregated row, which has no originating record.
145
- */
146
- export declare function readCreatedAt(rowValue: unknown): Date | null;
147
- /** When the record behind this row was last updated — the `__updated_at`
148
- * sibling of {@link readCreatedAt}. */
149
- export declare function readUpdatedAt(rowValue: unknown): Date | null;
150
- export declare const row: {
151
- opt: typeof opt;
152
- text: typeof text;
153
- num: typeof num;
154
- bool: typeof bool;
155
- date: typeof date;
156
- datetime: typeof datetime;
157
- link: typeof link;
158
- };
159
- export {};
package/dist/src/row.js DELETED
@@ -1,254 +0,0 @@
1
- /**
2
- * Accessors that coerce raw `useQuery` row values into typed values. Each cell
3
- * of a query row is `unknown`: the query layer serializes a select column as a
4
- * `{ key, label }` array (one entry per selected option), a date/datetime as a
5
- * `YYYY-MM-DD[THH:mm…]` string, a number/boolean as itself.
6
- *
7
- * These live in app-sdk — next to `useQuery`, whose serialization contract they
8
- * decode — so a change to that contract updates them in one place, and so apps
9
- * stop re-implementing the same coercions. They are pure (`unknown` in, value
10
- * out) and never throw.
11
- */
12
- /**
13
- * Select → first option key. Reads the enriched `{ key, label }` shape (and the
14
- * legacy bare id / single-element array / `{ id }` shapes for back-compat).
15
- * Use `readSelect` for the full `{ key, label }[]` on multi-select cells.
16
- */
17
- function opt(v) {
18
- if (typeof v === "string")
19
- return v || null;
20
- if (Array.isArray(v))
21
- return v.length ? opt(v[0]) : null;
22
- if (v && typeof v === "object") {
23
- const key = v.key;
24
- if (typeof key === "string")
25
- return key || null;
26
- const id = v.id;
27
- return typeof id === "string" ? id || null : null;
28
- }
29
- return null;
30
- }
31
- /** Text/markdown/autonumber → string (numbers stringified; everything else ""). */
32
- function text(v) {
33
- if (typeof v === "string")
34
- return v;
35
- if (typeof v === "number" && Number.isFinite(v))
36
- return String(v);
37
- return "";
38
- }
39
- /**
40
- * Number → number, or `null` when the cell holds no number — an empty cell, a
41
- * NaN/Infinity, an unparseable string. Empty and zero usually mean opposite
42
- * things ("not priced yet" vs "free", "no reading" vs "a reading of 0"), so the
43
- * absence is preserved the way `date` preserves it. Write `?? 0` where 0 IS the
44
- * correct reading of an empty cell (a sum, a count).
45
- */
46
- function num(v) {
47
- if (typeof v === "number")
48
- return Number.isFinite(v) ? v : null;
49
- if (typeof v === "string" && v.trim()) {
50
- const n = Number(v);
51
- return Number.isFinite(n) ? n : null;
52
- }
53
- return null;
54
- }
55
- /**
56
- * Checkbox/boolean → `true` / `false`, or `null` when the cell holds neither —
57
- * an empty cell, a value of another kind. (The strings "true"/"false" decode.)
58
- *
59
- * AN UNANSWERED CHECKBOX IS NOT A "NO", exactly as an empty number cell is not a
60
- * 0: the field's default is what the workspace writes when a row states
61
- * something, and a row nobody has answered states nothing. Read as `false` it
62
- * drew "No" for every partner nobody had assessed — the rows a gate exists to
63
- * catch are precisely the ones that answer nothing — so the absence is preserved
64
- * the way `num` and `date` preserve theirs. Write `=== true` where only the
65
- * affirmative acts, `?? false` where the safe side IS the reading.
66
- */
67
- function bool(v) {
68
- if (v === true || v === "true")
69
- return true;
70
- if (v === false || v === "false")
71
- return false;
72
- return null;
73
- }
74
- /**
75
- * Date/datetime field → a LOCAL-midnight Date for the stored calendar day, so
76
- * calendar/gantt placement never shifts across timezones. Parses the leading
77
- * `YYYY[-MM[-DD]]` of the serialized string — a reduced-precision `date` value
78
- * ("2026-05" / "2026") decodes to its PERIOD START (missing month/day → 1)
79
- * rather than vanishing to null. Null only if absent or unparseable. (Range
80
- * fields are not handled here — they have no consumer yet.)
81
- */
82
- function date(v) {
83
- const m = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?/.exec(text(v));
84
- if (!m)
85
- return null;
86
- return new Date(Number(m[1]), (m[2] ? Number(m[2]) : 1) - 1, m[3] ? Number(m[3]) : 1);
87
- }
88
- /**
89
- * Date/datetime field → a LOCAL Date that KEEPS the stored wall-clock time, so
90
- * `getHours()` / `toLocaleTimeString()` render the time as written. The stored
91
- * value is a timezone-less workspace wall-clock (see the serialization note
92
- * above), so it is read verbatim — no UTC conversion. Use this when the time
93
- * matters (a check-in time, an appointment); `date` keeps only the calendar day.
94
- * Parses `YYYY[-MM[-DD]]` with an optional `T`-or-space `HH:mm[:ss]`; a missing
95
- * month/day is the period start (1) and a missing time is midnight — a
96
- * reduced-precision value decodes rather than vanishing. Null if absent or
97
- * unparseable.
98
- */
99
- function datetime(v) {
100
- const m = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?(?:[ T](\d{2}):(\d{2}))?/.exec(text(v));
101
- if (!m)
102
- return null;
103
- return new Date(Number(m[1]), (m[2] ? Number(m[2]) : 1) - 1, m[3] ? Number(m[3]) : 1, m[4] ? Number(m[4]) : 0, m[5] ? Number(m[5]) : 0);
104
- }
105
- /**
106
- * One `{ id, display }` object → ResolvedLink, or null if absent/malformed.
107
- *
108
- * A MISSING `display` IS NOT A LINK. Other cells carry `{ id }` without one — a
109
- * member is the everyday case — and answering `display: ""` for them made every
110
- * assignee read as a blank-named link wherever a reader tried links first. A
111
- * link the server resolved always states its display, even as the empty string,
112
- * so requiring the KEY costs a real link nothing.
113
- */
114
- function asLink(v) {
115
- if (!v || typeof v !== "object")
116
- return null;
117
- const id = v.id;
118
- if (typeof id !== "string" || !id)
119
- return null;
120
- const display = v.display;
121
- return typeof display === "string" ? { id, display } : null;
122
- }
123
- /**
124
- * select_record_link → the FIRST linked record as `{ id, display }` (or null).
125
- * The common single-link case: read `.display` to render the linked record's
126
- * name, `.id` to correlate/filter (e.g. group rows by a parent record). Use
127
- * `readLinks` for the full list on multi-link fields.
128
- */
129
- function link(v) {
130
- return readLinks(v)[0] ?? null;
131
- }
132
- /**
133
- * select_record_link → ALL linked records as `{ id, display }[]` (empty if none).
134
- *
135
- * A LOOKUP OF A LINK FIELD ARRIVES ONE LEVEL DEEPER — the cell holds one entry
136
- * per linked record and each entry is THAT record's whole link cell, an array —
137
- * so read entry-by-entry it decoded to nothing while the value was plainly
138
- * there. The nesting is flattened here, at the boundary that owns the
139
- * serialization, rather than at each caller: one level, in order, and a record
140
- * two linked rows both point at is one link, so ids repeat at most once.
141
- * Anything else still reads as empty.
142
- */
143
- export function readLinks(v) {
144
- const cell = Array.isArray(v) ? v : [v];
145
- const out = [];
146
- const seen = new Set();
147
- for (const entry of cell) {
148
- const held = Array.isArray(entry) ? entry : [entry];
149
- for (const one of held) {
150
- const linked = asLink(one);
151
- if (linked === null || seen.has(linked.id))
152
- continue;
153
- seen.add(linked.id);
154
- out.push(linked);
155
- }
156
- }
157
- return out;
158
- }
159
- /**
160
- * files field → the attached files with their presigned `url` (empty if none).
161
- * Skips entries the server didn't presign (no `url`) so a consumer never renders
162
- * an unservable file. Pass one to `toDisplayFile` from `@lotics/ui/file_thumbnail`
163
- * for FileThumbnail/Gallery — the kit owns that conversion, so don't re-declare
164
- * it per app.
165
- *
166
- * A LOOKUP OF A FILES FIELD ARRIVES ONE LEVEL DEEPER, exactly as a lookup of a
167
- * link field does (see {@link readLinks}) — the cell holds one entry per linked
168
- * record and each entry is THAT record's whole files cell, an array — so read
169
- * entry-by-entry it decoded to nothing while the pictures were plainly there,
170
- * and every mark drawn off a looked-up photograph fell back to its glyph. The
171
- * nesting is flattened here, at the boundary that owns the serialization, rather
172
- * than at each caller: one level, in the order the rows link, and a file two
173
- * linked rows both carry is one file, so ids repeat at most once.
174
- */
175
- export function readFiles(v) {
176
- if (!Array.isArray(v))
177
- return [];
178
- const out = [];
179
- const seen = new Set();
180
- for (const entry of v) {
181
- const held = Array.isArray(entry) ? entry : [entry];
182
- for (const f of held) {
183
- if (!f || typeof f !== "object")
184
- continue;
185
- const id = f.id;
186
- const url = f.url;
187
- if (typeof id !== "string" || !id || typeof url !== "string" || !url || seen.has(id))
188
- continue;
189
- seen.add(id);
190
- const filename = f.filename;
191
- const mime = f.mime_type;
192
- const thumb = f.thumbnail_url;
193
- const size = f.size;
194
- const created_at = f.created_at;
195
- out.push({
196
- id,
197
- filename: typeof filename === "string" ? filename : "",
198
- mime_type: typeof mime === "string" ? mime : "",
199
- url,
200
- thumbnail_url: typeof thumb === "string" ? thumb : undefined,
201
- size: typeof size === "number" ? size : undefined,
202
- created_at: typeof created_at === "string" ? created_at : undefined,
203
- });
204
- }
205
- }
206
- return out;
207
- }
208
- /**
209
- * Record lock state for a `useQuery` row. The query layer emits a row-level
210
- * `__source_locked` addressing column (alongside `__source_record_id`). A locked
211
- * record rejects direct writes, so an app reads this to show a locked state and
212
- * route edits through a `request_locked_field_change` workflow instead of an
213
- * `update_records` save. Pass the whole row (not a cell). False for any
214
- * non-object / absent flag.
215
- */
216
- export function readLocked(rowValue) {
217
- if (!rowValue || typeof rowValue !== "object")
218
- return false;
219
- // A row that says NOTHING about its lock is not locked — this is the flag's
220
- // absence, not a record's unanswered question, so the safe side is the reading.
221
- return bool(rowValue["__source_locked"]) === true;
222
- }
223
- /**
224
- * The stored record's timestamp on a `useQuery` row, read off the addressing
225
- * column named. Unlike a `date`/`datetime` CELL — a timezone-less workspace
226
- * wall-clock at minute precision — these are true INSTANTS (ISO-8601 with an
227
- * offset), so they are parsed as such and render in the viewer's zone. Pass the
228
- * whole row (not a cell). Null for any non-object / absent / unparseable value.
229
- */
230
- function rowInstant(rowValue, column) {
231
- if (!rowValue || typeof rowValue !== "object")
232
- return null;
233
- const raw = rowValue[column];
234
- if (typeof raw !== "string" || !raw)
235
- return null;
236
- const d = new Date(raw);
237
- return Number.isNaN(d.getTime()) ? null : d;
238
- }
239
- /**
240
- * When the record behind this row was created — the row-level `__created_at`
241
- * column the compiler emits on every row-level query result. It is a DISPLAY
242
- * value, so a table with no date field of its own still has a chronological
243
- * anchor (a kardex date, an "as of" caption) with no schema change. Absent on a
244
- * grouped/aggregated row, which has no originating record.
245
- */
246
- export function readCreatedAt(rowValue) {
247
- return rowInstant(rowValue, "__created_at");
248
- }
249
- /** When the record behind this row was last updated — the `__updated_at`
250
- * sibling of {@link readCreatedAt}. */
251
- export function readUpdatedAt(rowValue) {
252
- return rowInstant(rowValue, "__updated_at");
253
- }
254
- export const row = { opt, text, num, bool, date, datetime, link };
package/dist/src/rpc.d.ts DELETED
@@ -1,207 +0,0 @@
1
- import type { AskUserChoiceOutput } from "./agent_stream.js";
2
- import { type UrlParams, type UrlParamsPatch } from "./url_params.js";
3
- /**
4
- * RPC bridge for a custom-code app's data operations.
5
- *
6
- * An app reaches the Lotics API one of two ways, chosen automatically:
7
- *
8
- * - **Bridged** — the app is embedded by a Lotics host (the authenticated
9
- * product, or `lotics app dev`), which passes its origin via the
10
- * `?lotics_host=` query param. The host holds the session; the app posts
11
- * ops over postMessage and the host makes the API call. Used by internal
12
- * apps — the member's credentials must never reach the app.
13
- *
14
- * - **Standalone** — the app is served on its own origin, with no host. It
15
- * calls the public `/v1/apps/{id}/*` endpoints directly, at the API address
16
- * the serving host injected into the page (`API_BASE_META`); those are
17
- * anonymous-accessible for a publicly-shared app. The app holds no
18
- * credentials, so there is nothing to protect.
19
- *
20
- * Wire protocol (bridged — must match `app_iframe_host.tsx`):
21
- * app → host: { id, op, payload }
22
- * host → app: { id, type: "result", data } | { id, type: "error", message }
23
- */
24
- export type RpcOp = "query" | "field_options" | "workflow" | "agentRuns" | "agentRun.get" | "agentRun.cancel" | "upload" | "members" | "context" | "openExternal" | "openApp" | "askAi" | "urlState.get" | "urlState.set" | "comments.list" | "comments.create" | "comments.update" | "comments.delete" | "comments.counts" | "recording.start" | "recording.stop";
25
- /** Payload for starting a streaming agent run. */
26
- export interface AgentRunPayload {
27
- alias: string;
28
- session_id: string;
29
- input: Record<string, unknown>;
30
- }
31
- /** Handle to a running stream — `done` settles at the end (or rejects on
32
- * error); `abort` stops it. The caller feeds each text chunk to the parser. */
33
- export interface AgentRunHandle {
34
- done: Promise<void>;
35
- abort: () => void;
36
- }
37
- /**
38
- * The app's identity, resolved once at startup to tag PostHog events.
39
- * Assembled by whichever transport is active:
40
- *
41
- * - **Bridged** — the host (authenticated) supplies `member_id`, plus
42
- * org/workspace/app from its own context.
43
- * - **Standalone** — the public `/by-subdomain` endpoint returns identity;
44
- * `member_id` is null (anonymous visitor).
45
- */
46
- /** A raw reference to one record — table + record id, passed through UNRESOLVED.
47
- * The member's own chat agent may act on it only where that member's IAM already
48
- * allows; the app never resolves it into data here. */
49
- export interface AiContextRecordRef {
50
- table_id: string;
51
- record_id: string;
52
- }
53
- /**
54
- * A snapshot of what the app RENDERED to this member on the current screen —
55
- * published to the ambient app chat agent via `useAiContext` so the member's
56
- * agent knows what they are looking at. Push-only: this is data the app already
57
- * showed, never a channel for chat to pull app-authority data back out.
58
- */
59
- export interface AiContextValue {
60
- /** Human-readable summary of the view — enters the agent prompt as labeled
61
- * DATA (never instructions). Host-capped to 1000 chars. */
62
- description: string;
63
- /** Records the screen is showing, as raw `{ table_id, record_id }` refs
64
- * (unresolved). Host-capped to 20. */
65
- records?: AiContextRecordRef[];
66
- /** Optional structured detail (filters, sort, form values). Host JSON-encodes
67
- * it and DROPS the field past its size cap, keeping the description. */
68
- data?: unknown;
69
- }
70
- /** A fire-and-forget notification the app pushes UP to its embedding host — no
71
- * id, no reply, distinct from the request/reply `rpc()` bridge. */
72
- export type HostNotification = {
73
- type: "aiContext";
74
- slot: string;
75
- context: AiContextValue | null;
76
- };
77
- export interface AppContext {
78
- member_id: string | null;
79
- /**
80
- * Whether the app declared the `comments` capability. `useComments` is
81
- * available only when this is true AND a member is signed in. False for any
82
- * app that didn't opt in, and (vacuously) for standalone visitors.
83
- */
84
- comments_enabled: boolean;
85
- /**
86
- * Whether this host records for the app: true only from a host that has the
87
- * `recording.*` ops, for a signed-in member, on an instance that transcribes.
88
- * A host that predates recording omits it, which reads as false.
89
- */
90
- recording_enabled?: boolean;
91
- /** The recordings this app started that the host still tracks, by alias —
92
- * the state a newly loaded frame starts from. */
93
- recordings?: Record<string, unknown>;
94
- }
95
- /**
96
- * Whether the app is running embedded in a Lotics host (vs. standalone at its
97
- * own origin). `rpc()`, `useUrlState`, and `AppRouter` use this to
98
- * pick the transport / behaviour; an app rarely needs it directly.
99
- */
100
- export declare function isEmbedded(): boolean;
101
- export declare function rpc<T = unknown>(op: RpcOp, payload: unknown): Promise<T>;
102
- /** Read the current app-owned query params — bridged: ask the host; standalone:
103
- * the page's own query string. */
104
- export declare function getUrlParams(): Promise<UrlParams>;
105
- /** Merge `patch` into the host's address bar (each key set, or cleared when its
106
- * value is `undefined`), preserving every other param. The host writes in place
107
- * (`history.replaceState`) — view-state changes don't add history entries. */
108
- export declare function setUrlParams(patch: UrlParamsPatch): Promise<void>;
109
- /** Synchronous best-effort snapshot for first paint. Standalone reads its own
110
- * URL (no flash); bridged can't read the cross-origin host URL synchronously,
111
- * so it returns `{}` and the hook hydrates via `getUrlParams()` on mount. */
112
- export declare function peekUrlParams(): UrlParams;
113
- /** Subscribe to external query changes — browser back/forward and edited URLs.
114
- * Embedded: the host's `url-state` broadcast; standalone: `popstate`. */
115
- export declare function subscribeUrlParams(cb: (params: UrlParams) => void): () => void;
116
- /**
117
- * Push a fire-and-forget notification to the embedding host — no id, no reply.
118
- * Only the embedded host can receive it (it owns the chat surface), so this
119
- * no-ops standalone (the app's own origin has no host to inform) and never
120
- * throws. Distinct from `rpc()`: this is one-way, app → host.
121
- */
122
- export declare function postHostNotification(message: HostNotification): void;
123
- /**
124
- * Subscribe to the host's `queriesChanged` push, which names the aliases whose
125
- * underlying tables moved. Returns an unsubscribe fn.
126
- *
127
- * The host owns the realtime connection — one per tab, shared by every surface
128
- * in it — because the app frame deliberately holds no platform credentials, and
129
- * a second socket per app would only duplicate a subscription the host already
130
- * has. The host resolves changed tables to aliases (it holds the query ASTs)
131
- * and names them here, so a mounted hook refetches only when ITS query is
132
- * affected rather than on every change anywhere in the workspace.
133
- *
134
- * A standalone (public) app has no host, so this is inert there — those apps
135
- * stay on pull-based freshness.
136
- */
137
- export declare function subscribeQueriesChanged(cb: (aliases: string[]) => void): () => void;
138
- /** Register a mounted query's alias. Returns the matching unregister. */
139
- export declare function registerQueryAlias(alias: string): () => void;
140
- /**
141
- * Re-read the mounted queries because THIS app just wrote.
142
- *
143
- * The host's `queriesChanged` push is for changes the app did not make — it
144
- * travels the realtime path (a version counter, a socket the host owns, and
145
- * coalescing that is deliberately lazy under load), which is right for another
146
- * member's edit and far too slow for your own. A writer already knows, so it
147
- * rings the same bell locally instead of waiting to be told.
148
- *
149
- * It names every mounted alias rather than only the ones the write touched: the
150
- * app cannot know which tables a workflow wrote, and the alternative — asking
151
- * every call site to declare what it invalidates — is a list that goes stale
152
- * silently the first time a workflow body grows a second write. The cost is
153
- * bounded by what is on screen, and it is the work the app was going to do a
154
- * moment later anyway.
155
- */
156
- export declare function notifyLocalWrite(): void;
157
- /**
158
- * Start a streaming agent run. Each raw SSE text chunk is handed to `onText`
159
- * (the caller parses it via `agent_stream`); `done` settles when the stream
160
- * ends. Bridged: the host opens the SSE with its session and forwards chunks;
161
- * standalone: the SDK reads the public endpoint's body directly.
162
- */
163
- export declare function rpcAgentRun(payload: AgentRunPayload, onText: (chunk: string) => void, onRunId?: (runId: string) => void): AgentRunHandle;
164
- export interface AgentRunContinuePayload {
165
- run_id: string;
166
- tool_call_id: string;
167
- /** The `ask_user_choice` output the user assembled (validated server-side). */
168
- output: AskUserChoiceOutput;
169
- }
170
- /**
171
- * Continue a PARKED (`awaiting_input`) agent run with the user's answer to its
172
- * pending `ask_user_choice` call — streams the continuation leg exactly like
173
- * `rpcAgentRun` streams the first.
174
- */
175
- export declare function rpcAgentRunContinue(payload: AgentRunContinuePayload, onText: (chunk: string) => void): AgentRunHandle;
176
- /**
177
- * The password-session header. It is deliberately NOT `Authorization`: this
178
- * token authenticates nobody — it proves the visitor knows the app's shared
179
- * link password — and the credential header already carries API keys and OAuth
180
- * bearers. Sharing it meant the server's auth middleware rejected the request
181
- * as an unknown bearer before the password gate ever ran, which took every
182
- * password-gated public app offline. Wire constant, mirrored server-side by
183
- * `APP_PUBLIC_SESSION_HEADER`; both sides are pinned by tests.
184
- */
185
- export declare const APP_PUBLIC_SESSION_HEADER = "x-lotics-app-session";
186
- /** The run token header — mirrored server-side by `APP_AGENT_RUN_TOKEN_HEADER`. */
187
- export declare const APP_AGENT_RUN_TOKEN_HEADER = "x-app-agent-run-token";
188
- /**
189
- * The error message for a non-ok response. A JSON error the API AUTHORED surfaces
190
- * verbatim — a 4xx carrying a `message`, or a 5xx that also carries `code`, the
191
- * discriminator every error the API emits is built with. A non-JSON body (a
192
- * gateway HTML page), a 5xx from something that is not us, or a body without a
193
- * `message` falls back to a body-free, status-derived message — so a raw HTML
194
- * body never becomes the message. `parsed` is the JSON.parse of the body, or
195
- * `null` if it wasn't JSON.
196
- */
197
- export declare function transportErrorMessage(status: number, parsed: unknown): string;
198
- /**
199
- * The error a stream that never started should throw.
200
- *
201
- * A streaming endpoint fails BEFORE the first byte like any other request — a 402 when the
202
- * workspace is out of credits, a 403, a 404. Those bodies are structured errors, and throwing
203
- * the body TEXT put the whole JSON on screen: `{"message":"Credit quota exceeded…","error":
204
- * "quota_exceeded","plan_id":"free",…}`. Same rule as every other call: the `message` is the
205
- * message, and a non-JSON or 5xx body never becomes one.
206
- */
207
- export declare function streamStartError(res: Response): Promise<Error>;