@colixsystems/widget-sdk 0.91.0 → 0.93.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 +23 -0
- package/dist/contract.cjs +44 -15
- package/dist/contract.js +44 -15
- package/dist/hooks.js +77 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +1 -0
- package/dist/index.native.js +1 -0
- package/dist/manifest.cjs +18 -64
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
|
|
|
41
41
|
| **DATASTORE** (`ctx.datastore`) | `useDatastoreQuery(table, options?)` | `{ data, loading, error, refetch }` | `records(table).list` (unwraps `{ data, meta }` to `data: []`) — `datastore.read:*` |
|
|
42
42
|
| **DATASTORE** | `useDatastoreRecord(table, id)` | `{ data, loading, error, refetch }` | `records(table).get` — `datastore.read:<table>` |
|
|
43
43
|
| **DATASTORE** | `useDatastoreSchema(tableId)` | `{ schema, loading, error, refetch }` | `schema(tableId)` — `datastore.read:<table>` |
|
|
44
|
+
| **DATASTORE** | `useInterpretDraft(tableId)` | `{ interpret, interpreting, error, result, available }` | `interpret(tableId, body)` — `datastore.read:<table>`. Turns ONE sentence a user typed ("walk at 11 am tomorrow") into DRAFT column values so a form can prefill itself. IMPERATIVE: call `interpret(text, { fields?, timeZone? })` from an event handler, never on mount. It DRAFTS and writes nothing — show the values for review, then submit through `useDatastoreMutation().create`. Resolves to `{ values, unresolved }`; `values` is keyed by column NAME (the shape `create()` takes) and `unresolved` names the fields the sentence did not state. Only text / number / boolean / date / datetime / array columns are drafted — `FILE`, `RELATION`, `USER` and `USER_GROUP` carry ids and are never guessed. Fails closed to an empty draft. **Every call spends the workspace's AI credits** and is rate-limited per actor, so call it once per user action (never on mount or in a render loop); once the workspace runs out the call is refused with a generic 429 — an app user is deliberately **not** told the workspace's billing state, since they have never heard of an AI credit and cannot buy one. Never surface a raw error to the person filling the form: say drafting is unavailable and keep every field editable by hand. `available` is false where the host brokers no interpreter. |
|
|
44
45
|
| **DATASTORE** | `useDatastoreMutation(table)` | `{ create, update, delete }` | `records(table).{ create, update (PATCH), delete }` — `datastore.write:*` |
|
|
45
46
|
| **DATASTORE** | `useRecordPermissions(tableId, recordId)` | `{ permissions, loading, error, grant, revoke, update, refetch }` | `records(table).permissions(record).{ list, grant, update, revoke }` — `acl.write:records` (+ `can_grant` on the record) |
|
|
46
47
|
| **FILES** (`ctx.assets`) | `useAsset(id)` | `{ url, file, loading, error, refetch }` | `ctx.assets.get` — no scope |
|
|
@@ -64,6 +65,28 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
64
65
|
|
|
65
66
|
`v0.91.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**.
|
|
66
67
|
|
|
68
|
+
### What's new in 0.93.0 (contract 1.66.0)
|
|
69
|
+
|
|
70
|
+
**BREAKING: a widget no longer declares server-side actions.** `manifest.actions` is removed from the contract and **refused** by `validateManifest` — an author who declares it now fails the publish with a message naming the replacement, rather than shipping a widget that quietly carries no automation.
|
|
71
|
+
|
|
72
|
+
- **Why.** `actions` made a widget two products in one package: a React component that renders, and a script that never renders at all — different runtime, different lifecycle, different review concerns, one manifest. Automation is now its own marketplace deliverable, an **Action** (`@colixsystems/action-sdk`), with its own manifest, starter kit, developer guide, submit button and platform-admin review. A workspace installs and configures it separately from any widget.
|
|
73
|
+
- **What to do instead.** Move the script into an Action manifest (`appstudio-action lint` / `pack`), publish it, and let the workspace install it. Its `propertySchema` is filled in by the **installing operator** rather than a page author, and the values reach the script as the `properties` global.
|
|
74
|
+
- **Removed from the contract**: the `actions` manifest field. `CONTRACT.actionTriggerTypes` / `actionScriptGlobals` / `actionScriptMaxBytes` remain exported for now but describe a surface no widget field uses — the action-sdk owns that vocabulary.
|
|
75
|
+
- **Existing installs are unaffected.** A tenant Action row materialised from a widget manifest keeps running, keeps its bindings and keeps its run history; the platform migrated those rows onto the Action that ships the automation. What is gone is the ability to declare a NEW one on a widget.
|
|
76
|
+
- **No parity impact.** Actions never ran in the rendered app, so nothing about the Player or the export changes.
|
|
77
|
+
|
|
78
|
+
### What's new in 0.92.0 (contract 1.65.0)
|
|
79
|
+
|
|
80
|
+
**New `useInterpretDraft(tableId)` hook — turn one sentence into DRAFT record values.** A new DATASTORE hook reading a new `interpret` method on the existing `ctx.datastore` slice (`@colixsystems/datastore-client` 0.13.0). Returns `{ interpret, interpreting, error, result, available }`. Call `interpret(text, { fields, timeZone })` **imperatively** from an event handler — never on mount or in a render loop — and it resolves to `{ values, unresolved }`, where `values` is keyed by column NAME (the same shape `useDatastoreMutation().create` takes) and `unresolved` names the fields the sentence did not state.
|
|
81
|
+
|
|
82
|
+
**It DRAFTS and writes nothing.** Prefill your inputs from `values`, let the person review and correct them, then submit as usual. A model reading free text must never create a record on its own.
|
|
83
|
+
|
|
84
|
+
Only columns a sentence can honestly produce are drafted — string, text, number, float, boolean, date, datetime and array. `FILE`, `RELATION`, `USER` and `USER_GROUP` are never guessed because they carry identifiers, and encrypted columns are skipped. Every value is coerced against its column's `data_type` and dropped when it does not fit, so a value the model got wrong is reported `unresolved` rather than written through.
|
|
85
|
+
|
|
86
|
+
**Every call spends the workspace's AI credits** and is rate-limited per actor. Once the workspace runs out, the call is refused with a **generic** 429: the person filling the form is never told the workspace's billing state — they have not heard of an AI credit and cannot buy one. Never surface a raw error to them; say drafting is unavailable and keep every field editable by hand. `available` is `false` where the host brokers no interpreter (an unbound preview, or an export with no reachable backend) — hide the affordance rather than rendering a dead button.
|
|
87
|
+
|
|
88
|
+
Additive — one new hook, one new client method, one new context-slice function; no existing export changed signature.
|
|
89
|
+
|
|
67
90
|
### What's new in 0.91.0 (contract 1.64.0)
|
|
68
91
|
|
|
69
92
|
**New `useSpeechToText()` hook — dictate into text with the device's on-device recogniser.** A new CORE hook reading a new `speech` capability on the existing `ctx.device` slice. Returns `{ transcript, partial, listening, supported, error, start, stop, abort, reset }`. Capture is **imperative** — call `start()` from a user gesture (a `Pressable.onPress`); the browser and the mobile OS gate the microphone prompt on a gesture, so it NEVER listens on mount. `transcript` accumulates finalised speech across utterances; `partial` holds the guess the recogniser has not committed yet (empty unless `options.interimResults`). `stop()` finalises and keeps what was heard, `abort()` discards the current utterance, `reset()` clears both. `options` (`{ lang, continuous, interimResults }`) pass through to the host. Rejections surface as a structured `SpeechToTextError` (new named export) with a stable `.code` (`PERMISSION_DENIED` / `NO_SPEECH` / `LANGUAGE_UNSUPPORTED` / `NETWORK` / `ABORTED` / `UNSUPPORTED` / `INTERNAL`). It needs **no manifest scope** and **no `requestedScopes` entry**.
|
package/dist/contract.cjs
CHANGED
|
@@ -820,6 +820,42 @@ const HOOKS = [
|
|
|
820
820
|
requiredContextSlice: ["datastore.schema"],
|
|
821
821
|
scopes: ["datastore.read:<table>"],
|
|
822
822
|
},
|
|
823
|
+
{
|
|
824
|
+
name: "useInterpretDraft",
|
|
825
|
+
signature: "useInterpretDraft(tableId)",
|
|
826
|
+
description:
|
|
827
|
+
"sc-4932 — turns ONE sentence a user typed into DRAFT values for the " +
|
|
828
|
+
"table's columns (\"walk at 11 am tomorrow\" -> { title: 'Walk', " +
|
|
829
|
+
"due_at: '...T11:00' }). IMPERATIVE: call interpret(text, { fields, " +
|
|
830
|
+
"timeZone }) from an event handler (a button press), never on mount. It " +
|
|
831
|
+
"DRAFTS and writes NOTHING — prefill your form from `values`, let the " +
|
|
832
|
+
"user review and correct it, then submit through " +
|
|
833
|
+
"useDatastoreMutation().create as usual. `values` is keyed by column " +
|
|
834
|
+
"NAME, the same shape create() takes. `unresolved` lists the fields the " +
|
|
835
|
+
"sentence did not state — leave those blank rather than guessing. Pass " +
|
|
836
|
+
"`fields` to narrow the draft to the columns you actually render, each " +
|
|
837
|
+
"optionally carrying a dropdown's closed `options` list; pass `timeZone` " +
|
|
838
|
+
"(an IANA zone) so relative times resolve correctly. Only text, number, " +
|
|
839
|
+
"boolean, date/datetime and array columns are drafted — FILE, RELATION, " +
|
|
840
|
+
"USER and USER_GROUP columns are never guessed because they carry ids. " +
|
|
841
|
+
"Every call spends the workspace's AI CREDITS and is rate-limited per " +
|
|
842
|
+
"actor, so call it once per user action — never on mount or in a render " +
|
|
843
|
+
"loop. Once the workspace runs out of credits the call is refused with a " +
|
|
844
|
+
"generic 429 (an app user is never told the workspace's billing state — " +
|
|
845
|
+
"they have not heard of an AI credit and cannot buy one). NEVER surface a " +
|
|
846
|
+
"raw error to the person filling the form: say drafting is unavailable and " +
|
|
847
|
+
"keep every field editable by hand. Reads ctx.datastore.interpret.",
|
|
848
|
+
returnShape: {
|
|
849
|
+
interpret:
|
|
850
|
+
"(text, { fields?, timeZone? }) => Promise<{ values, unresolved }>",
|
|
851
|
+
interpreting: "boolean",
|
|
852
|
+
error: "DatastoreError | null",
|
|
853
|
+
result: "{ values, unresolved } | null",
|
|
854
|
+
available: "boolean // false when the host brokers no interpreter",
|
|
855
|
+
},
|
|
856
|
+
requiredContextSlice: ["datastore.interpret"],
|
|
857
|
+
scopes: ["datastore.read:<table>"],
|
|
858
|
+
},
|
|
823
859
|
{
|
|
824
860
|
name: "useDatastoreMutation",
|
|
825
861
|
signature: "useDatastoreMutation(tableId)",
|
|
@@ -1554,17 +1590,6 @@ const MANIFEST_SCHEMA = {
|
|
|
1554
1590
|
description:
|
|
1555
1591
|
"Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 8 tables, 24 columns per table. RELATION columns address siblings by `targetSuffix` (must be declared earlier in the array). Tables persist across uninstalls.",
|
|
1556
1592
|
},
|
|
1557
|
-
actions: {
|
|
1558
|
-
type: "object[]",
|
|
1559
|
-
required: false,
|
|
1560
|
-
description:
|
|
1561
|
-
"Optional. Server-side actions the widget declares. Each runs in the shared isolated-vm action runner (cron- or record-triggered) — NEVER in the rendered app. Operators enable them per tenant from the Properties Panel; the action materialises DISABLED until they bind an integration API key (and, for record_* triggers, a target table) in the Actions admin page. Each entry: { key (stable, unique within the manifest), name, description?, triggerTypes (a non-empty array of unique values from " +
|
|
1562
|
-
ACTION_TRIGGER_TYPES.join(", ") +
|
|
1563
|
-
"; combine them so one script serves several events — the script's `triggerType` global names the event that actually fired), scheduleCron? (required iff triggerTypes contains 'schedule'; node-cron syntax), timeoutMs? (100–300000), scriptSource (≤200 KiB; runs against " +
|
|
1564
|
-
ACTION_SCRIPT_GLOBALS.join(", ") +
|
|
1565
|
-
" — NOT the React surface, so SDK imports/hooks are unavailable) }. Do NOT include triggerTableId or apiKeyId — those are tenant-local and bound after install.",
|
|
1566
|
-
default: [],
|
|
1567
|
-
},
|
|
1568
1593
|
translations: {
|
|
1569
1594
|
type: "object",
|
|
1570
1595
|
required: false,
|
|
@@ -1637,13 +1662,13 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
1637
1662
|
datastore: {
|
|
1638
1663
|
description:
|
|
1639
1664
|
"Injected @colixsystems/datastore-client instance. " +
|
|
1640
|
-
"{ tables: { list(), get(idOrName) }, schema(tableId) -> Promise<{ id, name, columns: [...] }>, " +
|
|
1665
|
+
"{ tables: { list(), get(idOrName), interpret(idOrName, body) }, schema(tableId) -> Promise<{ id, name, columns: [...] }>, " +
|
|
1641
1666
|
"records(tableId) -> { list(query) -> Promise<{ data, meta }>, get(id), create(values), update(id, values), delete(id), aggregate(spec), " +
|
|
1642
1667
|
"permissions(recordId) -> { list() -> Promise<{ data, meta }>, grant(body), update(permId, patch), revoke(permId) } } }. " +
|
|
1643
|
-
"`records` backs the query/record/mutation hooks; `records(t).permissions(r)` backs useRecordPermissions(); `schema` backs useDatastoreSchema(). " +
|
|
1668
|
+
"`records` backs the query/record/mutation hooks; `records(t).permissions(r)` backs useRecordPermissions(); `schema` backs useDatastoreSchema(); `interpret` backs useInterpretDraft() (sc-4932 — drafts column values from one sentence; writes nothing). " +
|
|
1644
1669
|
"List methods return the { data, meta } envelope verbatim (hooks unwrap res.data); rows/bodies are snake_case (author column values keep their author-given names).",
|
|
1645
1670
|
required: true,
|
|
1646
|
-
fields: { records: "function", schema: "function", tables: "object" },
|
|
1671
|
+
fields: { records: "function", schema: "function", tables: "object", interpret: "function" },
|
|
1647
1672
|
},
|
|
1648
1673
|
directory: {
|
|
1649
1674
|
description:
|
|
@@ -2855,7 +2880,11 @@ const CONTRACT = deepFreeze({
|
|
|
2855
2880
|
// published widget keeps validating. The script's `triggerType` global
|
|
2856
2881
|
// now names the trigger that actually FIRED the run ('manual' and 'app'
|
|
2857
2882
|
// included), which is what makes a multi-trigger script able to branch.
|
|
2858
|
-
|
|
2883
|
+
// 1.66.0: BREAKING — `actions` is removed from the manifest contract and
|
|
2884
|
+
// REFUSED by the validator (sc-4879). Server-side automation ships as its own
|
|
2885
|
+
// marketplace deliverable (`@colixsystems/action-sdk`), which a workspace
|
|
2886
|
+
// installs and configures separately. A widget renders; it does not automate.
|
|
2887
|
+
version: "1.66.0",
|
|
2859
2888
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
2860
2889
|
hooks: HOOKS,
|
|
2861
2890
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -820,6 +820,42 @@ const HOOKS = [
|
|
|
820
820
|
requiredContextSlice: ["datastore.schema"],
|
|
821
821
|
scopes: ["datastore.read:<table>"],
|
|
822
822
|
},
|
|
823
|
+
{
|
|
824
|
+
name: "useInterpretDraft",
|
|
825
|
+
signature: "useInterpretDraft(tableId)",
|
|
826
|
+
description:
|
|
827
|
+
"sc-4932 — turns ONE sentence a user typed into DRAFT values for the " +
|
|
828
|
+
"table's columns (\"walk at 11 am tomorrow\" -> { title: 'Walk', " +
|
|
829
|
+
"due_at: '...T11:00' }). IMPERATIVE: call interpret(text, { fields, " +
|
|
830
|
+
"timeZone }) from an event handler (a button press), never on mount. It " +
|
|
831
|
+
"DRAFTS and writes NOTHING — prefill your form from `values`, let the " +
|
|
832
|
+
"user review and correct it, then submit through " +
|
|
833
|
+
"useDatastoreMutation().create as usual. `values` is keyed by column " +
|
|
834
|
+
"NAME, the same shape create() takes. `unresolved` lists the fields the " +
|
|
835
|
+
"sentence did not state — leave those blank rather than guessing. Pass " +
|
|
836
|
+
"`fields` to narrow the draft to the columns you actually render, each " +
|
|
837
|
+
"optionally carrying a dropdown's closed `options` list; pass `timeZone` " +
|
|
838
|
+
"(an IANA zone) so relative times resolve correctly. Only text, number, " +
|
|
839
|
+
"boolean, date/datetime and array columns are drafted — FILE, RELATION, " +
|
|
840
|
+
"USER and USER_GROUP columns are never guessed because they carry ids. " +
|
|
841
|
+
"Every call spends the workspace's AI CREDITS and is rate-limited per " +
|
|
842
|
+
"actor, so call it once per user action — never on mount or in a render " +
|
|
843
|
+
"loop. Once the workspace runs out of credits the call is refused with a " +
|
|
844
|
+
"generic 429 (an app user is never told the workspace's billing state — " +
|
|
845
|
+
"they have not heard of an AI credit and cannot buy one). NEVER surface a " +
|
|
846
|
+
"raw error to the person filling the form: say drafting is unavailable and " +
|
|
847
|
+
"keep every field editable by hand. Reads ctx.datastore.interpret.",
|
|
848
|
+
returnShape: {
|
|
849
|
+
interpret:
|
|
850
|
+
"(text, { fields?, timeZone? }) => Promise<{ values, unresolved }>",
|
|
851
|
+
interpreting: "boolean",
|
|
852
|
+
error: "DatastoreError | null",
|
|
853
|
+
result: "{ values, unresolved } | null",
|
|
854
|
+
available: "boolean // false when the host brokers no interpreter",
|
|
855
|
+
},
|
|
856
|
+
requiredContextSlice: ["datastore.interpret"],
|
|
857
|
+
scopes: ["datastore.read:<table>"],
|
|
858
|
+
},
|
|
823
859
|
{
|
|
824
860
|
name: "useDatastoreMutation",
|
|
825
861
|
signature: "useDatastoreMutation(tableId)",
|
|
@@ -1554,17 +1590,6 @@ const MANIFEST_SCHEMA = {
|
|
|
1554
1590
|
description:
|
|
1555
1591
|
"Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 8 tables, 24 columns per table. RELATION columns address siblings by `targetSuffix` (must be declared earlier in the array). Tables persist across uninstalls.",
|
|
1556
1592
|
},
|
|
1557
|
-
actions: {
|
|
1558
|
-
type: "object[]",
|
|
1559
|
-
required: false,
|
|
1560
|
-
description:
|
|
1561
|
-
"Optional. Server-side actions the widget declares. Each runs in the shared isolated-vm action runner (cron- or record-triggered) — NEVER in the rendered app. Operators enable them per tenant from the Properties Panel; the action materialises DISABLED until they bind an integration API key (and, for record_* triggers, a target table) in the Actions admin page. Each entry: { key (stable, unique within the manifest), name, description?, triggerTypes (a non-empty array of unique values from " +
|
|
1562
|
-
ACTION_TRIGGER_TYPES.join(", ") +
|
|
1563
|
-
"; combine them so one script serves several events — the script's `triggerType` global names the event that actually fired), scheduleCron? (required iff triggerTypes contains 'schedule'; node-cron syntax), timeoutMs? (100–300000), scriptSource (≤200 KiB; runs against " +
|
|
1564
|
-
ACTION_SCRIPT_GLOBALS.join(", ") +
|
|
1565
|
-
" — NOT the React surface, so SDK imports/hooks are unavailable) }. Do NOT include triggerTableId or apiKeyId — those are tenant-local and bound after install.",
|
|
1566
|
-
default: [],
|
|
1567
|
-
},
|
|
1568
1593
|
translations: {
|
|
1569
1594
|
type: "object",
|
|
1570
1595
|
required: false,
|
|
@@ -1637,13 +1662,13 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
1637
1662
|
datastore: {
|
|
1638
1663
|
description:
|
|
1639
1664
|
"Injected @colixsystems/datastore-client instance. " +
|
|
1640
|
-
"{ tables: { list(), get(idOrName) }, schema(tableId) -> Promise<{ id, name, columns: [...] }>, " +
|
|
1665
|
+
"{ tables: { list(), get(idOrName), interpret(idOrName, body) }, schema(tableId) -> Promise<{ id, name, columns: [...] }>, " +
|
|
1641
1666
|
"records(tableId) -> { list(query) -> Promise<{ data, meta }>, get(id), create(values), update(id, values), delete(id), aggregate(spec), " +
|
|
1642
1667
|
"permissions(recordId) -> { list() -> Promise<{ data, meta }>, grant(body), update(permId, patch), revoke(permId) } } }. " +
|
|
1643
|
-
"`records` backs the query/record/mutation hooks; `records(t).permissions(r)` backs useRecordPermissions(); `schema` backs useDatastoreSchema(). " +
|
|
1668
|
+
"`records` backs the query/record/mutation hooks; `records(t).permissions(r)` backs useRecordPermissions(); `schema` backs useDatastoreSchema(); `interpret` backs useInterpretDraft() (sc-4932 — drafts column values from one sentence; writes nothing). " +
|
|
1644
1669
|
"List methods return the { data, meta } envelope verbatim (hooks unwrap res.data); rows/bodies are snake_case (author column values keep their author-given names).",
|
|
1645
1670
|
required: true,
|
|
1646
|
-
fields: { records: "function", schema: "function", tables: "object" },
|
|
1671
|
+
fields: { records: "function", schema: "function", tables: "object", interpret: "function" },
|
|
1647
1672
|
},
|
|
1648
1673
|
directory: {
|
|
1649
1674
|
description:
|
|
@@ -2855,7 +2880,11 @@ const CONTRACT = deepFreeze({
|
|
|
2855
2880
|
// published widget keeps validating. The script's `triggerType` global
|
|
2856
2881
|
// now names the trigger that actually FIRED the run ('manual' and 'app'
|
|
2857
2882
|
// included), which is what makes a multi-trigger script able to branch.
|
|
2858
|
-
|
|
2883
|
+
// 1.66.0: BREAKING — `actions` is removed from the manifest contract and
|
|
2884
|
+
// REFUSED by the validator (sc-4879). Server-side automation ships as its own
|
|
2885
|
+
// marketplace deliverable (`@colixsystems/action-sdk`), which a workspace
|
|
2886
|
+
// installs and configures separately. A widget renders; it does not automate.
|
|
2887
|
+
version: "1.66.0",
|
|
2859
2888
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
2860
2889
|
hooks: HOOKS,
|
|
2861
2890
|
primitives: PRIMITIVES,
|
package/dist/hooks.js
CHANGED
|
@@ -1611,6 +1611,83 @@ export function useDatastoreSchema(tableId) {
|
|
|
1611
1611
|
return { schema, loading, error, refetch };
|
|
1612
1612
|
}
|
|
1613
1613
|
|
|
1614
|
+
/**
|
|
1615
|
+
* sc-4932 — draft record values for `tableId` from one sentence the user typed.
|
|
1616
|
+
* Returns `{ interpret, interpreting, error, result, available }`.
|
|
1617
|
+
*
|
|
1618
|
+
* interpret(text, { fields?, timeZone? }) → Promise<{ values, unresolved }>
|
|
1619
|
+
* `values` is keyed by column NAME — the same shape
|
|
1620
|
+
* `useDatastoreMutation().create` takes — so a caller prefills its form
|
|
1621
|
+
* from it and submits through the ordinary create path. `unresolved` names
|
|
1622
|
+
* the fields the sentence did not state; they are left for the user.
|
|
1623
|
+
*
|
|
1624
|
+
* The hook is IMPERATIVE — it NEVER fires on mount; the widget calls
|
|
1625
|
+
* `interpret` from an event handler. `interpreting` tracks an in-flight call
|
|
1626
|
+
* and `error` holds the last DatastoreError (cleared at the start of each
|
|
1627
|
+
* call). `result` holds the last successful draft.
|
|
1628
|
+
*
|
|
1629
|
+
* This DRAFTS, it does not write: the user reviews every value before the
|
|
1630
|
+
* record is created. `fields` narrows the draft to the columns the caller
|
|
1631
|
+
* actually renders, each optionally carrying the closed `options` list of a
|
|
1632
|
+
* dropdown. `timeZone` is an IANA zone; the server resolves "at 11 am" against
|
|
1633
|
+
* it and falls back to UTC when it is absent or unknown.
|
|
1634
|
+
*
|
|
1635
|
+
* Routes through the injected `@colixsystems/datastore-client` at
|
|
1636
|
+
* `ctx.datastore.interpret`, so the Player and the Expo export resolve against
|
|
1637
|
+
* the identical client. A host that brokers no interpreter (an unbound canvas
|
|
1638
|
+
* preview) reports `available: false` rather than throwing, matching how
|
|
1639
|
+
* useTranslate degrades.
|
|
1640
|
+
*/
|
|
1641
|
+
export function useInterpretDraft(tableId) {
|
|
1642
|
+
const ctx = useWidgetContextOrThrow("useInterpretDraft");
|
|
1643
|
+
const available =
|
|
1644
|
+
Boolean(ctx.datastore) && typeof ctx.datastore.interpret === "function";
|
|
1645
|
+
|
|
1646
|
+
const fnRef = useRef(available ? ctx.datastore.interpret : null);
|
|
1647
|
+
fnRef.current = available ? ctx.datastore.interpret : null;
|
|
1648
|
+
const tableIdRef = useRef(tableId);
|
|
1649
|
+
tableIdRef.current = tableId;
|
|
1650
|
+
|
|
1651
|
+
const [interpreting, setInterpreting] = useState(false);
|
|
1652
|
+
const [error, setError] = useState(null);
|
|
1653
|
+
const [result, setResult] = useState(null);
|
|
1654
|
+
|
|
1655
|
+
const interpret = useCallback(async (text, options) => {
|
|
1656
|
+
const table = tableIdRef.current;
|
|
1657
|
+
if (!fnRef.current || !table) {
|
|
1658
|
+
throw new DatastoreError(
|
|
1659
|
+
"UNAVAILABLE",
|
|
1660
|
+
"No interpreter available for this host",
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
setInterpreting(true);
|
|
1664
|
+
setError(null);
|
|
1665
|
+
try {
|
|
1666
|
+
// snake_case verbatim on the wire (REQ-GEN-09) — the SDK does not
|
|
1667
|
+
// transform, so the camelCase `timeZone` argument is mapped here, once.
|
|
1668
|
+
const body = { text };
|
|
1669
|
+
if (options && Array.isArray(options.fields)) body.fields = options.fields;
|
|
1670
|
+
if (options && options.timeZone) body.time_zone = options.timeZone;
|
|
1671
|
+
const draft = await fnRef.current(table, body);
|
|
1672
|
+
const safe = {
|
|
1673
|
+
values: draft && draft.values ? draft.values : {},
|
|
1674
|
+
unresolved:
|
|
1675
|
+
draft && Array.isArray(draft.unresolved) ? draft.unresolved : [],
|
|
1676
|
+
};
|
|
1677
|
+
setResult(safe);
|
|
1678
|
+
setInterpreting(false);
|
|
1679
|
+
return safe;
|
|
1680
|
+
} catch (err) {
|
|
1681
|
+
const e = toDatastoreError(err);
|
|
1682
|
+
setError(e);
|
|
1683
|
+
setInterpreting(false);
|
|
1684
|
+
throw e;
|
|
1685
|
+
}
|
|
1686
|
+
}, []);
|
|
1687
|
+
|
|
1688
|
+
return { interpret, interpreting, error, result, available };
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1614
1691
|
/**
|
|
1615
1692
|
* Datastore mutation hook. Returns { create, update, delete }, each method
|
|
1616
1693
|
* returning a Promise. Routes through the injected
|
package/dist/index.d.ts
CHANGED
|
@@ -963,6 +963,50 @@ export function useDatastoreSchema(
|
|
|
963
963
|
tableId: string | null | undefined,
|
|
964
964
|
): SchemaResult;
|
|
965
965
|
|
|
966
|
+
/** sc-4932 — one field a draft may target, with a dropdown's closed option set. */
|
|
967
|
+
export interface InterpretDraftField {
|
|
968
|
+
column: string;
|
|
969
|
+
options?: Array<string | number>;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/** sc-4932 — options for `useInterpretDraft().interpret`. */
|
|
973
|
+
export interface InterpretDraftOptions {
|
|
974
|
+
fields?: Array<InterpretDraftField | string>;
|
|
975
|
+
/** IANA zone the server resolves relative times against. Defaults to UTC. */
|
|
976
|
+
timeZone?: string;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* sc-4932 — a drafted record. `values` is keyed by column NAME (the shape
|
|
981
|
+
* `useDatastoreMutation().create` takes); `unresolved` names the fields the
|
|
982
|
+
* sentence did not state.
|
|
983
|
+
*/
|
|
984
|
+
export interface InterpretDraftResult {
|
|
985
|
+
values: Record<string, unknown>;
|
|
986
|
+
unresolved: string[];
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
export interface InterpretDraftApi {
|
|
990
|
+
interpret(
|
|
991
|
+
text: string,
|
|
992
|
+
options?: InterpretDraftOptions,
|
|
993
|
+
): Promise<InterpretDraftResult>;
|
|
994
|
+
interpreting: boolean;
|
|
995
|
+
error: DatastoreError | null;
|
|
996
|
+
result: InterpretDraftResult | null;
|
|
997
|
+
/** False when the host brokers no interpreter (e.g. an unbound preview). */
|
|
998
|
+
available: boolean;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* sc-4932 — draft record values for `tableId` from one sentence the user typed
|
|
1003
|
+
* ("walk at 11 am tomorrow"). IMPERATIVE: never fires on mount, and DRAFTS
|
|
1004
|
+
* only — the user reviews the values before the record is created.
|
|
1005
|
+
*/
|
|
1006
|
+
export function useInterpretDraft(
|
|
1007
|
+
tableId: string | null | undefined,
|
|
1008
|
+
): InterpretDraftApi;
|
|
1009
|
+
|
|
966
1010
|
// REQ-RT-07 realtime subscription transport state.
|
|
967
1011
|
export type DatastoreSubscriptionStatus =
|
|
968
1012
|
| "connecting"
|
package/dist/index.js
CHANGED
package/dist/index.native.js
CHANGED
package/dist/manifest.cjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// re-exports the same functions defined here so there is exactly one
|
|
5
5
|
// implementation.
|
|
6
6
|
|
|
7
|
-
const { CONTRACT
|
|
7
|
+
const { CONTRACT } = require("./contract.cjs");
|
|
8
8
|
|
|
9
9
|
const PAYLOAD_VALUE_TYPES = CONTRACT.payloadValueTypes;
|
|
10
10
|
|
|
@@ -39,20 +39,6 @@ const VALID_CATEGORIES = new Set([
|
|
|
39
39
|
]);
|
|
40
40
|
const VALID_PLATFORMS = new Set(["web", "native"]);
|
|
41
41
|
|
|
42
|
-
// REQ-WIDGET-ACTION — structural validation for manifest-declared server
|
|
43
|
-
// actions. Mirrors the backend action.service caps; the runner re-validates
|
|
44
|
-
// the cron expression (node-cron) and binds the tenant-local API key + table
|
|
45
|
-
// at enable time, so we only check shape + size here (cross-env: no Buffer).
|
|
46
|
-
const VALID_ACTION_TRIGGERS = new Set([
|
|
47
|
-
"schedule",
|
|
48
|
-
"record_created",
|
|
49
|
-
"record_updated",
|
|
50
|
-
"record_deleted",
|
|
51
|
-
]);
|
|
52
|
-
const ACTION_SCRIPT_MAX_BYTES = 200 * 1024;
|
|
53
|
-
const ACTION_TIMEOUT_MIN_MS = 100;
|
|
54
|
-
const ACTION_TIMEOUT_MAX_MS = 5 * 60 * 1000;
|
|
55
|
-
|
|
56
42
|
// REQ-L10N-WIDGET — caps + patterns for manifest-declared translations.
|
|
57
43
|
// The relative key pattern is deliberately tighter than the dictionary's
|
|
58
44
|
// own KEY_RE: once the host namespaces it (`widget.<id>.<key>`) the result
|
|
@@ -133,54 +119,22 @@ function validateManifestTranslations(translations, manifestId, errors) {
|
|
|
133
119
|
}
|
|
134
120
|
}
|
|
135
121
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
seenKeys.add(a.key);
|
|
153
|
-
}
|
|
154
|
-
pushIf(errors, isNonEmptyString(a.name), "manifest.actions[].name must be a non-empty string");
|
|
155
|
-
const triggerTypes = normaliseActionTriggerTypes(a);
|
|
156
|
-
if (triggerTypes === null) {
|
|
157
|
-
errors.push(
|
|
158
|
-
`manifest.actions[].triggerTypes must be a non-empty array of unique values from ${[...VALID_ACTION_TRIGGERS].join(", ")}`,
|
|
159
|
-
);
|
|
160
|
-
} else if (triggerTypes.includes("schedule")) {
|
|
161
|
-
pushIf(
|
|
162
|
-
errors,
|
|
163
|
-
isNonEmptyString(a.scheduleCron),
|
|
164
|
-
"manifest.actions[].scheduleCron is required when triggerTypes contains 'schedule'",
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
if (!isNonEmptyString(a.scriptSource)) {
|
|
168
|
-
errors.push("manifest.actions[].scriptSource must be a non-empty string");
|
|
169
|
-
} else if (utf8ByteLength(a.scriptSource) > ACTION_SCRIPT_MAX_BYTES) {
|
|
170
|
-
errors.push("manifest.actions[].scriptSource exceeds 200 KiB");
|
|
171
|
-
}
|
|
172
|
-
if (a.timeoutMs !== undefined) {
|
|
173
|
-
const t = Number(a.timeoutMs);
|
|
174
|
-
if (!Number.isFinite(t) || t < ACTION_TIMEOUT_MIN_MS || t > ACTION_TIMEOUT_MAX_MS) {
|
|
175
|
-
errors.push("manifest.actions[].timeoutMs must be between 100 and 300000");
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (a.triggerTableId !== undefined || a.apiKeyId !== undefined) {
|
|
179
|
-
errors.push(
|
|
180
|
-
"manifest.actions[] must not include triggerTableId or apiKeyId — those are tenant-local and bound after install",
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
122
|
+
// REQ-MKT-ACTION (sc-4879): a widget no longer declares server-side actions.
|
|
123
|
+
//
|
|
124
|
+
// `manifest.actions` was how an automation shipped before Actions existed as
|
|
125
|
+
// their own marketplace deliverable — a widget was two products in one package,
|
|
126
|
+
// a component that renders and a script that never renders at all. It is now
|
|
127
|
+
// REFUSED rather than ignored: an author who declares it believes the
|
|
128
|
+
// automation will ship, and silence would publish that misunderstanding.
|
|
129
|
+
//
|
|
130
|
+
// The replacement is a marketplace Action (`@colixsystems/action-sdk`), which
|
|
131
|
+
// the workspace installs and configures on its own.
|
|
132
|
+
function validateManifestActions(_actions, errors) {
|
|
133
|
+
errors.push(
|
|
134
|
+
"manifest.actions is no longer supported — ship the automation as a " +
|
|
135
|
+
"marketplace Action (@colixsystems/action-sdk) instead of declaring it " +
|
|
136
|
+
"on a widget",
|
|
137
|
+
);
|
|
184
138
|
}
|
|
185
139
|
|
|
186
140
|
function canonicalCategory(c) {
|
|
@@ -355,7 +309,7 @@ function validateManifest(m) {
|
|
|
355
309
|
}
|
|
356
310
|
}
|
|
357
311
|
|
|
358
|
-
//
|
|
312
|
+
// Present at all is now an error (sc-4879) — see validateManifestActions.
|
|
359
313
|
if (manifest.actions !== undefined) {
|
|
360
314
|
validateManifestActions(manifest.actions, errors);
|
|
361
315
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.93.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|