@colixsystems/widget-sdk 0.267.0 → 0.268.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/README.md +68 -2
- package/dist/contract.cjs +14 -5
- package/dist/contract.js +14 -5
- package/dist/hooks.js +30 -2
- package/dist/index.d.ts +22 -3
- package/package.json +1 -1
- package/src/contract.js +14 -5
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
|
|
|
50
50
|
| **CORE** | `useTranslate()` | `{ translate, translating, error, language, available }` | `ctx.i18n.translate` — no scope. Machine-translates **user-generated content** (record text, file names, API payloads) into the app user's language; `useI18n().t()` is still the answer for your own copy. `translate(str)` → `Promise<string>`, `translate(str[])` → `Promise<string[]>` in ONE request. Target defaults to the app user's language. Cached per session, per pod, and durably per workspace, so repeat text is free. Limits: 50 segments / 5 000 chars each / 20 000 total. Rejects with `TranslateError`; `available` is false where the host cannot translate. |
|
|
51
51
|
| **CORE** | `useLinkPreview(url)` | `{ preview, loading, error, available }` — `preview` is `{ url, title, description, site_name, image_url } \| null` | `ctx.linkPreviews.get` — no scope. A link's title, description, site name and image, fetched by the platform (the device never contacts the linked site). A page with nothing to show resolves with null fields, not an error — render the plain link. A non-http(s) `url` makes no request. Memoized for up to an hour across widgets; concurrent calls for one URL share a request. `error` is a `LinkPreviewError` (`VALIDATION` \| `AUTH_REQUIRED` \| `RATE_LIMITED` \| `NETWORK` \| `INTERNAL`). `available` is false on a host with no link-preview client |
|
|
52
52
|
| **CORE** | `useStableQuery(buildQuery)` | `T \| undefined` (whatever `buildQuery()` returns) | No context slice, no scope. Keeps `buildQuery()`'s result at a STABLE reference across renders when its (JSON-serialised) content hasn't changed, so `useDatastoreQuery(tableId, useStableQuery(() => ({...})))` replaces a hand-rolled `useMemo` with an easy-to-get-wrong deps array. Never throws: a `buildQuery` that itself throws degrades to a stable `undefined`; a result that can't be diffed (e.g. circular) degrades to "always a new reference". |
|
|
53
|
-
| **DATASTORE** (`ctx.datastore`) | `useDatastoreQuery(table, options)` (optional: options) | `{ data, total, loading, fetching, error, refetch }` — `loading` only until the first result for the table; a changed query / refetch keeps `data` while `fetching`; `total` is the envelope's `meta.total` | `records(table).list` (unwraps `{ data, meta }` to `data: []`) — `datastore.read
|
|
53
|
+
| **DATASTORE** (`ctx.datastore`) | `useDatastoreQuery(table, options)` (optional: options) | `{ data, total, loading, fetching, error, refetch }` — `loading` only until the first result for the table; a changed query / refetch keeps `data` while `fetching`; `total` is the envelope's `meta.total` | `records(table).list` (unwraps `{ data, meta }` to `data: []`) — `datastore.read:*`. `options` is `{ filter, filterMode, q, sort, limit, offset, includeCapabilities }`, applied on the server: see *Querying records*. |
|
|
54
54
|
| **DATASTORE** | `useDatastoreFigure(table, { aggregate, column, filter })` (optional: options) | `{ value, text, truncated, loading, fetching, error, refetch }` | `records(table).list` (a count reads the envelope total; a sum or average up to 1000 rows) — `datastore.read:*`. One count / sum / average for a `<Stat>`, computed exactly as the built-in Stat widget does. |
|
|
55
55
|
| **DATASTORE** | `useDatastoreRecord(table, id, options)` (optional: options) | `{ data, loading, fetching, error, refetch }` — a refetch of the same record keeps `data` while `fetching`; `{ includeCapabilities: true }` adds `data._capabilities` | `records(table).get` — `datastore.read:<table>` |
|
|
56
56
|
| **DATASTORE** | `useDatastoreSchema(tableId)` | `{ schema, loading, error, refetch }` | `schema(tableId)` — `datastore.read:<table>` |
|
|
@@ -86,7 +86,15 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
86
86
|
|
|
87
87
|
## Status
|
|
88
88
|
|
|
89
|
-
`v0.
|
|
89
|
+
`v0.268.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
|
|
90
|
+
|
|
91
|
+
### What's new in 0.268.0 (contract 1.218.0)
|
|
92
|
+
|
|
93
|
+
**`useAction().available` tells the truth about the Action.** It used to be true whenever an Action was bound, so a widget offered a run that could never happen. It now turns false once the server says a run would not reach the script: the Action is switched off or paused, not allowed from the app, gone, or the viewer is not a signed-in app user (signed-out visitors and anonymous accounts can't run Actions). It is asked again when the binding or the signed-in user changes, and stays true while no answer has come. The actions client (`@colixsystems/actions-client` 0.2.0) gains `status(actionId)` for it.
|
|
94
|
+
|
|
95
|
+
### What's new in 0.267.1 (contract 1.217.0)
|
|
96
|
+
|
|
97
|
+
**The record query options are documented where an author reads them.** A new *Querying records* section lists everything `useDatastoreQuery` sends to the server — `filter` and its operators, `filterMode`, `q`, `sort`, `limit` (50 by default, at most 1000) and `offset` — with `total` for paging and a searchable, paged list to start from. The `Query` type now declares `q`, `offset`, `filterMode` and both filter and sort shapes, and marks `cursor`, which the datastore never read, deprecated. No runtime change.
|
|
90
98
|
|
|
91
99
|
### What's new in 0.267.0 (contract 1.217.0)
|
|
92
100
|
|
|
@@ -3543,6 +3551,64 @@ export default function OpenTasks({ tableId, titleField, statusField }) {
|
|
|
3543
3551
|
|
|
3544
3552
|
`useCanWrite(tableId)` answers "may I create a row here". It says nothing about changing an EXISTING row: gate a list's Edit / Delete on each row's `_capabilities` (read with `includeCapabilities: true`, see *What's new in 0.174.0*), and a single-record page on `useCanWrite(tableId, { recordId })` or `useDatastoreRecord(…, { includeCapabilities: true })`. Never call `useCanWrite` with a `recordId` per list row — that is one request per row. Either answer is a FLOOR, not the whole rule: a widget whose own logic is more specific ("only the assignee may edit this row") still hand-checks that in addition (`user.id === record[assigneeField]`).
|
|
3545
3553
|
|
|
3554
|
+
## Querying records
|
|
3555
|
+
|
|
3556
|
+
`useDatastoreQuery(tableId, query)` hands `query` to the datastore, which filters, searches, sorts and pages the table before a row reaches the widget. Every key is optional. Build the object with `useStableQuery` so it does not refetch every render.
|
|
3557
|
+
|
|
3558
|
+
| Option | What the server does with it |
|
|
3559
|
+
| --- | --- |
|
|
3560
|
+
| `filter` | Keeps the rows that match. A map `{ column: "op:value" }` (an array of expressions puts a range on one column), or an array `[{ column, operator, value }]`. |
|
|
3561
|
+
| `filterMode` | `"and"` (the default) keeps rows matching every condition; `"or"` keeps rows matching any. |
|
|
3562
|
+
| `q` | Free-text search: a case-insensitive substring match across every text column of the table (encrypted columns excepted). |
|
|
3563
|
+
| `sort` | `{ field, dir }` with `dir` `"asc"` or `"desc"`, or the string `"field:desc"`. One column; `created_at` and `updated_at` work too. |
|
|
3564
|
+
| `limit` | Rows per page: 50 when left out, at most 1000. |
|
|
3565
|
+
| `offset` | Rows to skip, for the next page. |
|
|
3566
|
+
| `includeCapabilities` | `true` stamps each row with `_capabilities`, what the signed-in user may do to it. |
|
|
3567
|
+
|
|
3568
|
+
The filter operators are `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `contains` (a case-insensitive substring), `empty` and `nempty` (no value). In the array form a condition may carry `valueMode`: `"currentUser"` or `"currentUserGroup"` with `eq` or `neq` matches the signed-in user (or their groups) on a USER or USER_GROUP column, and `"relativeDate"` with an ordering operator reads `value` as "that many days ago".
|
|
3569
|
+
|
|
3570
|
+
The result's `total` is how many rows match in all, not just this page: show "12 of 312" and page with `offset`. For a count, a sum or an average, use `useDatastoreFigure(tableId, { aggregate, column, filter })` instead of reading rows.
|
|
3571
|
+
|
|
3572
|
+
**Narrow on the server, never over a fetched batch.** A widget that fetches `limit: 1000` and then filters or searches the rows in JavaScript only ever sees the first 1000; once the table grows past that, it misses rows without saying so. Send the search box's text as `q`, a chip's condition as `filter`, and page with `offset`. The checker warns about the batch pattern.
|
|
3573
|
+
|
|
3574
|
+
```js
|
|
3575
|
+
import { useEffect, useState } from "react";
|
|
3576
|
+
import { Pressable, Text, TextInput, View, useDatastoreQuery, useStableQuery } from "@colixsystems/widget-sdk";
|
|
3577
|
+
|
|
3578
|
+
const PAGE = 50;
|
|
3579
|
+
|
|
3580
|
+
export default function ContactList({ tableId }) {
|
|
3581
|
+
const [draft, setDraft] = useState("");
|
|
3582
|
+
const [search, setSearch] = useState("");
|
|
3583
|
+
const [status, setStatus] = useState("");
|
|
3584
|
+
const [page, setPage] = useState(0);
|
|
3585
|
+
useEffect(() => {
|
|
3586
|
+
const wait = setTimeout(() => { setSearch(draft.trim()); setPage(0); }, 250);
|
|
3587
|
+
return () => clearTimeout(wait);
|
|
3588
|
+
}, [draft]);
|
|
3589
|
+
const query = useStableQuery(() => ({
|
|
3590
|
+
q: search,
|
|
3591
|
+
filter: status ? { status: `eq:${status}` } : undefined,
|
|
3592
|
+
sort: { field: "last_contacted", dir: "desc" },
|
|
3593
|
+
limit: PAGE,
|
|
3594
|
+
offset: page * PAGE,
|
|
3595
|
+
}));
|
|
3596
|
+
const { data, total, loading } = useDatastoreQuery(tableId, query);
|
|
3597
|
+
const pick = (next) => { setStatus(next); setPage(0); };
|
|
3598
|
+
return (
|
|
3599
|
+
<View>
|
|
3600
|
+
<TextInput value={draft} onChangeText={setDraft} placeholder="Search" />
|
|
3601
|
+
<Pressable onPress={() => pick(status === "Won" ? "" : "Won")}><Text>Won</Text></Pressable>
|
|
3602
|
+
{loading ? <Text>Loading…</Text> : data.map((row) => <Text key={row.id}>{row.name}</Text>)}
|
|
3603
|
+
<Text>{total ?? 0} contacts</Text>
|
|
3604
|
+
{(page + 1) * PAGE < (total ?? 0) ? (
|
|
3605
|
+
<Pressable onPress={() => setPage(page + 1)}><Text>Next page</Text></Pressable>
|
|
3606
|
+
) : null}
|
|
3607
|
+
</View>
|
|
3608
|
+
);
|
|
3609
|
+
}
|
|
3610
|
+
```
|
|
3611
|
+
|
|
3546
3612
|
## Cross-platform widgets (single-file vs split)
|
|
3547
3613
|
|
|
3548
3614
|
Every widget runs in **both** the web Player and the exported native (Expo) app.
|
package/dist/contract.cjs
CHANGED
|
@@ -2184,7 +2184,12 @@ const HOOKS = [
|
|
|
2184
2184
|
"`actionRef` is an Action id an author bound through an `actionRef` " +
|
|
2185
2185
|
"property — when it is missing, or the host brokers no actions client, " +
|
|
2186
2186
|
"`available` is false and run() rejects UNAVAILABLE; render a visible " +
|
|
2187
|
-
"'pick an action' state, NEVER a blank box.
|
|
2187
|
+
"'pick an action' state, NEVER a blank box. sc-14224: `available` also " +
|
|
2188
|
+
"turns false once the server says a run would not reach the script — " +
|
|
2189
|
+
"the Action is switched off or paused, not allowed from the app, gone, " +
|
|
2190
|
+
"or the viewer is not a signed-in app user (signed-out visitors and " +
|
|
2191
|
+
"anonymous accounts can't run Actions) — so render an 'isn't available' " +
|
|
2192
|
+
"state then too. It stays true while no answer has come. A changed binding clears " +
|
|
2188
2193
|
"`result` and `error`, and a run that settles after the binding changed " +
|
|
2189
2194
|
"(or after unmount) writes nothing. ActionError.code: " +
|
|
2190
2195
|
"INVALID_ACTION_INPUT (400) — the input was refused; " +
|
|
@@ -2195,7 +2200,7 @@ const HOOKS = [
|
|
|
2195
2200
|
"MARKDOWN, so render it with <RichText value={result} />, not <Text>. " +
|
|
2196
2201
|
"Reads ctx.actions.",
|
|
2197
2202
|
returnShape: {
|
|
2198
|
-
available: "boolean // false when no Action is bound,
|
|
2203
|
+
available: "boolean // false when no Action is bound, the host brokers none, or the server says a run would not reach the script",
|
|
2199
2204
|
run: "(input) => Promise<unknown> // input is optional; resolves with the Action's respond() value, or null; rejects with ActionError",
|
|
2200
2205
|
running: "boolean // true while any run is in flight",
|
|
2201
2206
|
result: "unknown // the last successful run's result; null before the first run",
|
|
@@ -4150,13 +4155,14 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
4150
4155
|
actions: {
|
|
4151
4156
|
description:
|
|
4152
4157
|
"sc-13843 — injected @colixsystems/actions-client instance. " +
|
|
4153
|
-
"{ run(actionId, input?) -> Promise<{ ok, result }
|
|
4158
|
+
"{ run(actionId, input?) -> Promise<{ ok, result }>, status(actionId) -> " +
|
|
4159
|
+
"Promise<{ available, reason }> }. Backs useAction(). " +
|
|
4154
4160
|
"Runs one Action as the signed-in caller; the server refuses an Action " +
|
|
4155
4161
|
"without the app trigger. Errors carry the server's `code` and `status`. " +
|
|
4156
4162
|
"Absent on a host that brokers no actions client, where useAction() " +
|
|
4157
4163
|
"degrades to available: false rather than throwing.",
|
|
4158
4164
|
required: false,
|
|
4159
|
-
fields: { run: "function" },
|
|
4165
|
+
fields: { run: "function", status: "function" },
|
|
4160
4166
|
},
|
|
4161
4167
|
linkPreviews: {
|
|
4162
4168
|
description:
|
|
@@ -6472,7 +6478,10 @@ const CONTRACT = deepFreeze({
|
|
|
6472
6478
|
// 1.217.0: additive (sc-4747) — `TextInput` documents `keyboardType`,
|
|
6473
6479
|
// `autoCapitalize`, `autoCorrect`, `autoComplete`, `secureTextEntry`;
|
|
6474
6480
|
// `useDatastoreSchema` columns document `text_format` (snake_case wire).
|
|
6475
|
-
|
|
6481
|
+
// 1.218.0: additive (sc-14224) — the actions client's `status(actionId)`;
|
|
6482
|
+
// `useAction().available` turns false when the server says a run would
|
|
6483
|
+
// not reach the script.
|
|
6484
|
+
version: "1.218.0",
|
|
6476
6485
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6477
6486
|
hooks: HOOKS,
|
|
6478
6487
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -2181,7 +2181,12 @@ const HOOKS = [
|
|
|
2181
2181
|
"`actionRef` is an Action id an author bound through an `actionRef` " +
|
|
2182
2182
|
"property — when it is missing, or the host brokers no actions client, " +
|
|
2183
2183
|
"`available` is false and run() rejects UNAVAILABLE; render a visible " +
|
|
2184
|
-
"'pick an action' state, NEVER a blank box.
|
|
2184
|
+
"'pick an action' state, NEVER a blank box. sc-14224: `available` also " +
|
|
2185
|
+
"turns false once the server says a run would not reach the script — " +
|
|
2186
|
+
"the Action is switched off or paused, not allowed from the app, gone, " +
|
|
2187
|
+
"or the viewer is not a signed-in app user (signed-out visitors and " +
|
|
2188
|
+
"anonymous accounts can't run Actions) — so render an 'isn't available' " +
|
|
2189
|
+
"state then too. It stays true while no answer has come. A changed binding clears " +
|
|
2185
2190
|
"`result` and `error`, and a run that settles after the binding changed " +
|
|
2186
2191
|
"(or after unmount) writes nothing. ActionError.code: " +
|
|
2187
2192
|
"INVALID_ACTION_INPUT (400) — the input was refused; " +
|
|
@@ -2192,7 +2197,7 @@ const HOOKS = [
|
|
|
2192
2197
|
"MARKDOWN, so render it with <RichText value={result} />, not <Text>. " +
|
|
2193
2198
|
"Reads ctx.actions.",
|
|
2194
2199
|
returnShape: {
|
|
2195
|
-
available: "boolean // false when no Action is bound,
|
|
2200
|
+
available: "boolean // false when no Action is bound, the host brokers none, or the server says a run would not reach the script",
|
|
2196
2201
|
run: "(input) => Promise<unknown> // input is optional; resolves with the Action's respond() value, or null; rejects with ActionError",
|
|
2197
2202
|
running: "boolean // true while any run is in flight",
|
|
2198
2203
|
result: "unknown // the last successful run's result; null before the first run",
|
|
@@ -4147,13 +4152,14 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
4147
4152
|
actions: {
|
|
4148
4153
|
description:
|
|
4149
4154
|
"sc-13843 — injected @colixsystems/actions-client instance. " +
|
|
4150
|
-
"{ run(actionId, input?) -> Promise<{ ok, result }
|
|
4155
|
+
"{ run(actionId, input?) -> Promise<{ ok, result }>, status(actionId) -> " +
|
|
4156
|
+
"Promise<{ available, reason }> }. Backs useAction(). " +
|
|
4151
4157
|
"Runs one Action as the signed-in caller; the server refuses an Action " +
|
|
4152
4158
|
"without the app trigger. Errors carry the server's `code` and `status`. " +
|
|
4153
4159
|
"Absent on a host that brokers no actions client, where useAction() " +
|
|
4154
4160
|
"degrades to available: false rather than throwing.",
|
|
4155
4161
|
required: false,
|
|
4156
|
-
fields: { run: "function" },
|
|
4162
|
+
fields: { run: "function", status: "function" },
|
|
4157
4163
|
},
|
|
4158
4164
|
linkPreviews: {
|
|
4159
4165
|
description:
|
|
@@ -6469,7 +6475,10 @@ const CONTRACT = deepFreeze({
|
|
|
6469
6475
|
// 1.217.0: additive (sc-4747) — `TextInput` documents `keyboardType`,
|
|
6470
6476
|
// `autoCapitalize`, `autoCorrect`, `autoComplete`, `secureTextEntry`;
|
|
6471
6477
|
// `useDatastoreSchema` columns document `text_format` (snake_case wire).
|
|
6472
|
-
|
|
6478
|
+
// 1.218.0: additive (sc-14224) — the actions client's `status(actionId)`;
|
|
6479
|
+
// `useAction().available` turns false when the server says a run would
|
|
6480
|
+
// not reach the script.
|
|
6481
|
+
version: "1.218.0",
|
|
6473
6482
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6474
6483
|
hooks: HOOKS,
|
|
6475
6484
|
primitives: PRIMITIVES,
|
package/dist/hooks.js
CHANGED
|
@@ -3893,18 +3893,46 @@ function toActionError(err) {
|
|
|
3893
3893
|
* `available` is false and `run` rejects UNAVAILABLE rather than throwing at
|
|
3894
3894
|
* render — the widget must render a visible unbound state (CLAUDE.md §8).
|
|
3895
3895
|
*
|
|
3896
|
+
* sc-14224 — `available` also turns false once the server says a run would
|
|
3897
|
+
* not reach the script: the Action is switched off, paused, not allowed from
|
|
3898
|
+
* the app, gone, or the viewer is not a signed-in app user. It is asked again
|
|
3899
|
+
* when the binding or the signed-in user changes. With no answer (offline, an
|
|
3900
|
+
* older host) it stays true and `run` reports the refusal.
|
|
3901
|
+
*
|
|
3896
3902
|
* Routes through the injected `@colixsystems/actions-client` at `ctx.actions`,
|
|
3897
3903
|
* so the Player and the Expo export resolve against the identical client.
|
|
3898
3904
|
*/
|
|
3899
3905
|
export function useAction(actionRef) {
|
|
3900
3906
|
const ctx = useWidgetContextOrThrow("useAction");
|
|
3901
|
-
const
|
|
3907
|
+
const bound =
|
|
3902
3908
|
Boolean(actionRef) &&
|
|
3903
3909
|
Boolean(ctx.actions) &&
|
|
3904
3910
|
typeof ctx.actions.run === "function";
|
|
3911
|
+
const canAsk = bound && typeof ctx.actions.status === "function";
|
|
3912
|
+
const askedAs = `${actionRef}|${(ctx.user && ctx.user.id) || ""}`;
|
|
3905
3913
|
|
|
3906
3914
|
const clientRef = useRef(null);
|
|
3907
|
-
clientRef.current =
|
|
3915
|
+
clientRef.current = bound ? ctx.actions : null;
|
|
3916
|
+
|
|
3917
|
+
// The binding and viewer the server last refused, so a rebind starts fresh.
|
|
3918
|
+
const [refusedAs, setRefusedAs] = useState(null);
|
|
3919
|
+
useEffect(() => {
|
|
3920
|
+
const client = clientRef.current;
|
|
3921
|
+
if (!canAsk || !client) return undefined;
|
|
3922
|
+
let live = true;
|
|
3923
|
+
client.status(actionRef).then(
|
|
3924
|
+
(answer) => {
|
|
3925
|
+
if (live) setRefusedAs(answer && answer.available === false ? askedAs : null);
|
|
3926
|
+
},
|
|
3927
|
+
// No answer is not a refusal: keep offering the run.
|
|
3928
|
+
() => {},
|
|
3929
|
+
);
|
|
3930
|
+
return () => {
|
|
3931
|
+
live = false;
|
|
3932
|
+
};
|
|
3933
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3934
|
+
}, [askedAs, canAsk]);
|
|
3935
|
+
const available = bound && refusedAs !== askedAs;
|
|
3908
3936
|
const actionIdRef = useRef(actionRef);
|
|
3909
3937
|
actionIdRef.current = actionRef;
|
|
3910
3938
|
|
package/dist/index.d.ts
CHANGED
|
@@ -890,6 +890,8 @@ export interface WidgetContext<TProps = unknown> {
|
|
|
890
890
|
/** sc-13843 — injected @colixsystems/actions-client backing useAction(). */
|
|
891
891
|
actions?: {
|
|
892
892
|
run(actionId: string, input?: Record<string, unknown> | null): Promise<{ ok: boolean; result: unknown }>;
|
|
893
|
+
/** sc-14224 — whether a run would reach the script; optional on older hosts. */
|
|
894
|
+
status?(actionId: string): Promise<{ available: boolean; reason: string | null }>;
|
|
893
895
|
};
|
|
894
896
|
/** Injected @colixsystems/link-preview-client backing useLinkPreview(). */
|
|
895
897
|
linkPreviews?: { get(url: string): Promise<LinkPreview> };
|
|
@@ -1074,10 +1076,23 @@ export function hasCornerRadius(
|
|
|
1074
1076
|
radius: ResolvedCornerRadius | null | undefined,
|
|
1075
1077
|
): boolean;
|
|
1076
1078
|
|
|
1079
|
+
/** A record query the datastore applies on the server; see the README's "Querying records". */
|
|
1077
1080
|
export interface Query {
|
|
1078
|
-
|
|
1079
|
-
|
|
1081
|
+
/** `{ column: "op:value" }` (an array of expressions for a range), or `[{ column, operator, value, valueMode }]`. */
|
|
1082
|
+
filter?:
|
|
1083
|
+
| Record<string, string | string[]>
|
|
1084
|
+
| Array<{ column: string; operator?: string; value?: unknown; valueMode?: string }>;
|
|
1085
|
+
/** `"and"` (the default) keeps rows matching every condition, `"or"` rows matching any. */
|
|
1086
|
+
filterMode?: "and" | "or";
|
|
1087
|
+
/** A case-insensitive substring search across the table's text columns. */
|
|
1088
|
+
q?: string;
|
|
1089
|
+
/** `{ field, dir }` or `"field:desc"`; one column, so an array uses its first entry. */
|
|
1090
|
+
sort?: string | { field: string; dir?: "asc" | "desc" } | Array<{ field: string; dir?: "asc" | "desc" }>;
|
|
1091
|
+
/** Rows per page: 50 when omitted, at most 1000. */
|
|
1080
1092
|
limit?: number;
|
|
1093
|
+
/** Rows to skip, for the next page. */
|
|
1094
|
+
offset?: number;
|
|
1095
|
+
/** @deprecated The datastore ignores it; page with `offset`. */
|
|
1081
1096
|
cursor?: string;
|
|
1082
1097
|
/**
|
|
1083
1098
|
* Stamp each row with `_capabilities` — what the signed-in user may do to
|
|
@@ -1880,7 +1895,11 @@ export interface AgentApi {
|
|
|
1880
1895
|
export function useAgent(agentRef: string | null | undefined): AgentApi;
|
|
1881
1896
|
|
|
1882
1897
|
export interface ActionApi {
|
|
1883
|
-
/**
|
|
1898
|
+
/**
|
|
1899
|
+
* False when no Action is bound, the host brokers no actions client, or the
|
|
1900
|
+
* server says a run would not reach the script (switched off, not allowed
|
|
1901
|
+
* from the app, or the viewer is not a signed-in app user).
|
|
1902
|
+
*/
|
|
1884
1903
|
available: boolean;
|
|
1885
1904
|
/**
|
|
1886
1905
|
* Imperative — call from an event handler, never on mount. Resolves with the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.268.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"homepage": "https://github.com/Colix-AB/AppStudio",
|
|
6
6
|
"type": "module",
|
package/src/contract.js
CHANGED
|
@@ -2181,7 +2181,12 @@ const HOOKS = [
|
|
|
2181
2181
|
"`actionRef` is an Action id an author bound through an `actionRef` " +
|
|
2182
2182
|
"property — when it is missing, or the host brokers no actions client, " +
|
|
2183
2183
|
"`available` is false and run() rejects UNAVAILABLE; render a visible " +
|
|
2184
|
-
"'pick an action' state, NEVER a blank box.
|
|
2184
|
+
"'pick an action' state, NEVER a blank box. sc-14224: `available` also " +
|
|
2185
|
+
"turns false once the server says a run would not reach the script — " +
|
|
2186
|
+
"the Action is switched off or paused, not allowed from the app, gone, " +
|
|
2187
|
+
"or the viewer is not a signed-in app user (signed-out visitors and " +
|
|
2188
|
+
"anonymous accounts can't run Actions) — so render an 'isn't available' " +
|
|
2189
|
+
"state then too. It stays true while no answer has come. A changed binding clears " +
|
|
2185
2190
|
"`result` and `error`, and a run that settles after the binding changed " +
|
|
2186
2191
|
"(or after unmount) writes nothing. ActionError.code: " +
|
|
2187
2192
|
"INVALID_ACTION_INPUT (400) — the input was refused; " +
|
|
@@ -2192,7 +2197,7 @@ const HOOKS = [
|
|
|
2192
2197
|
"MARKDOWN, so render it with <RichText value={result} />, not <Text>. " +
|
|
2193
2198
|
"Reads ctx.actions.",
|
|
2194
2199
|
returnShape: {
|
|
2195
|
-
available: "boolean // false when no Action is bound,
|
|
2200
|
+
available: "boolean // false when no Action is bound, the host brokers none, or the server says a run would not reach the script",
|
|
2196
2201
|
run: "(input) => Promise<unknown> // input is optional; resolves with the Action's respond() value, or null; rejects with ActionError",
|
|
2197
2202
|
running: "boolean // true while any run is in flight",
|
|
2198
2203
|
result: "unknown // the last successful run's result; null before the first run",
|
|
@@ -4147,13 +4152,14 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
4147
4152
|
actions: {
|
|
4148
4153
|
description:
|
|
4149
4154
|
"sc-13843 — injected @colixsystems/actions-client instance. " +
|
|
4150
|
-
"{ run(actionId, input?) -> Promise<{ ok, result }
|
|
4155
|
+
"{ run(actionId, input?) -> Promise<{ ok, result }>, status(actionId) -> " +
|
|
4156
|
+
"Promise<{ available, reason }> }. Backs useAction(). " +
|
|
4151
4157
|
"Runs one Action as the signed-in caller; the server refuses an Action " +
|
|
4152
4158
|
"without the app trigger. Errors carry the server's `code` and `status`. " +
|
|
4153
4159
|
"Absent on a host that brokers no actions client, where useAction() " +
|
|
4154
4160
|
"degrades to available: false rather than throwing.",
|
|
4155
4161
|
required: false,
|
|
4156
|
-
fields: { run: "function" },
|
|
4162
|
+
fields: { run: "function", status: "function" },
|
|
4157
4163
|
},
|
|
4158
4164
|
linkPreviews: {
|
|
4159
4165
|
description:
|
|
@@ -6469,7 +6475,10 @@ const CONTRACT = deepFreeze({
|
|
|
6469
6475
|
// 1.217.0: additive (sc-4747) — `TextInput` documents `keyboardType`,
|
|
6470
6476
|
// `autoCapitalize`, `autoCorrect`, `autoComplete`, `secureTextEntry`;
|
|
6471
6477
|
// `useDatastoreSchema` columns document `text_format` (snake_case wire).
|
|
6472
|
-
|
|
6478
|
+
// 1.218.0: additive (sc-14224) — the actions client's `status(actionId)`;
|
|
6479
|
+
// `useAction().available` turns false when the server says a run would
|
|
6480
|
+
// not reach the script.
|
|
6481
|
+
version: "1.218.0",
|
|
6473
6482
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6474
6483
|
hooks: HOOKS,
|
|
6475
6484
|
primitives: PRIMITIVES,
|