@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.
- package/AGENTS.md +32 -47
- package/dist/agent_stream.d.ts +131 -0
- package/dist/ask_ai.d.ts +27 -0
- package/dist/attachments.d.ts +58 -0
- package/dist/chunk-ARV5FAU5.js +1132 -0
- package/dist/comments.d.ts +89 -0
- package/dist/error_report.d.ts +9 -0
- package/dist/folder_pick.d.ts +8 -0
- package/dist/geolocation.d.ts +42 -0
- package/dist/hooks.d.ts +251 -0
- package/dist/{src/index.d.ts → index.d.ts} +13 -22
- package/dist/index.js +31309 -0
- package/dist/index.js.LEGAL.txt +11 -0
- package/dist/members.d.ts +32 -0
- package/dist/mock.d.ts +37 -0
- package/dist/mount.d.ts +19 -0
- package/dist/new_record.d.ts +37 -0
- package/dist/open_app.d.ts +12 -0
- package/dist/open_external.d.ts +10 -0
- package/dist/overlay.d.ts +25 -0
- package/dist/queries.d.ts +231 -0
- package/dist/recording.d.ts +47 -0
- package/dist/recording_state.d.ts +43 -0
- package/dist/rename_file.d.ts +13 -0
- package/dist/router.d.ts +10 -0
- package/dist/router.js +97 -0
- package/dist/row.d.ts +87 -0
- package/dist/rpc.d.ts +114 -0
- package/dist/select.d.ts +24 -0
- package/dist/shared_types.d.ts +8 -0
- package/dist/store.d.ts +43 -0
- package/dist/types.d.ts +36 -0
- package/dist/upload/optimize.d.ts +30 -0
- package/dist/upload/pipeline.d.ts +36 -0
- package/dist/upload/transport.d.ts +19 -0
- package/dist/url_params.d.ts +55 -0
- package/dist/use_recents.d.ts +15 -0
- package/dist/{src/use_url_state.d.ts → use_url_state.d.ts} +0 -2
- package/dist/viewer.d.ts +41 -0
- package/dist/written.d.ts +77 -0
- package/docs/ai.md +74 -133
- package/docs/data_fetching.md +209 -290
- package/docs/files.md +61 -51
- package/docs/members_and_options.md +93 -63
- package/docs/mutations.md +135 -205
- package/docs/navigation_and_state.md +26 -35
- package/docs/queries.md +144 -207
- package/docs/recipes.md +21 -45
- package/docs/runtime.md +74 -137
- package/docs/security.md +8 -11
- package/docs/workflows.md +189 -174
- package/package.json +27 -28
- package/dist/src/agent_stream.d.ts +0 -200
- package/dist/src/agent_stream.js +0 -314
- package/dist/src/ask_ai.d.ts +0 -40
- package/dist/src/ask_ai.js +0 -35
- package/dist/src/attachments.d.ts +0 -68
- package/dist/src/attachments.js +0 -93
- package/dist/src/comments.d.ts +0 -127
- package/dist/src/comments.js +0 -192
- package/dist/src/download.js +0 -54
- package/dist/src/geolocation.d.ts +0 -64
- package/dist/src/geolocation.js +0 -96
- package/dist/src/hooks.d.ts +0 -781
- package/dist/src/hooks.js +0 -860
- package/dist/src/index.js +0 -34
- package/dist/src/members.d.ts +0 -105
- package/dist/src/members.js +0 -62
- package/dist/src/mock.d.ts +0 -118
- package/dist/src/mock.js +0 -124
- package/dist/src/mount.d.ts +0 -47
- package/dist/src/mount.js +0 -34
- package/dist/src/new_record.d.ts +0 -74
- package/dist/src/new_record.js +0 -117
- package/dist/src/open_app.d.ts +0 -15
- package/dist/src/open_app.js +0 -18
- package/dist/src/open_external.d.ts +0 -16
- package/dist/src/open_external.js +0 -19
- package/dist/src/recording.d.ts +0 -59
- package/dist/src/recording.js +0 -30
- package/dist/src/recording_state.d.ts +0 -59
- package/dist/src/recording_state.js +0 -94
- package/dist/src/router.d.ts +0 -17
- package/dist/src/router.js +0 -144
- package/dist/src/row.d.ts +0 -159
- package/dist/src/row.js +0 -254
- package/dist/src/rpc.d.ts +0 -207
- package/dist/src/rpc.js +0 -904
- package/dist/src/select.d.ts +0 -34
- package/dist/src/select.js +0 -40
- package/dist/src/types.d.ts +0 -115
- package/dist/src/types.js +0 -1
- package/dist/src/upload/optimize.d.ts +0 -54
- package/dist/src/upload/optimize.js +0 -207
- package/dist/src/upload/pipeline.d.ts +0 -55
- package/dist/src/upload/pipeline.js +0 -52
- package/dist/src/upload/transport.d.ts +0 -42
- package/dist/src/upload/transport.js +0 -128
- package/dist/src/url_params.d.ts +0 -93
- package/dist/src/url_params.js +0 -215
- package/dist/src/use_optimistic.d.ts +0 -27
- package/dist/src/use_optimistic.js +0 -27
- package/dist/src/use_recents.d.ts +0 -19
- package/dist/src/use_recents.js +0 -71
- package/dist/src/use_url_state.js +0 -73
- package/dist/src/viewer.d.ts +0 -26
- package/dist/src/viewer.js +0 -47
- /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>;
|