infaira-canvas 0.2.0 → 0.3.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/dist/commands/init.js +17 -1
- package/package.json +2 -2
- package/templates/ICan-Widget-Development-Guide.md +58 -1
- package/templates/ican.d.ts +31 -1
- package/templates/index.html +16 -0
- package/templates/ui.html +15 -0
package/dist/commands/init.js
CHANGED
|
@@ -288,9 +288,15 @@ import LocalizationMessages from '../localization.json';
|
|
|
288
288
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
289
289
|
|
|
290
290
|
export interface IActionOptions {
|
|
291
|
+
/** Parse response body as JSON before resolving. */
|
|
291
292
|
json?: boolean;
|
|
293
|
+
/** Stable identifier for cancellation. Required when cancelPrevious is set. */
|
|
292
294
|
key?: string;
|
|
295
|
+
/** When true and "key" is also set, any prior in-flight call sharing the
|
|
296
|
+
* same key is cancelled (its Promise resolves to null). Use for live-filter
|
|
297
|
+
* widgets where only the latest call result should land in state. */
|
|
293
298
|
cancelPrevious?: boolean;
|
|
299
|
+
/** Skip the 100ms batch window and fire immediately. */
|
|
294
300
|
executeImmediately?: boolean;
|
|
295
301
|
}
|
|
296
302
|
|
|
@@ -316,13 +322,23 @@ export interface IContextProvider {
|
|
|
316
322
|
* - executeAction('Buildings', 'List', { siteId }) → ICan native
|
|
317
323
|
* - executeAction('mythos:Inventory', 'UpdateStock', p) → Mythos engine
|
|
318
324
|
* The 'mythos:' prefix is the only difference at the call site; the
|
|
319
|
-
* ICan backend routes the call appropriately.
|
|
325
|
+
* ICan backend routes the call appropriately.
|
|
326
|
+
* options.cancelPrevious + options.key: when set, an in-flight call with
|
|
327
|
+
* the same key is cancelled (resolves null) and the new call wins. */
|
|
320
328
|
executeAction(model: string, action: string, parameters: unknown, options?: IActionOptions): Promise<unknown>;
|
|
321
329
|
executeService(app: string, service: string, parameters: unknown, options?: IActionOptions): Promise<unknown>;
|
|
322
330
|
/** List Mythos actions callable via executeAction('mythos:...', ...).
|
|
323
331
|
* Optional — present only when MYTHOS_BASE_URL is configured on the ICan
|
|
324
332
|
* backend. Treat absence as "Mythos integration disabled". */
|
|
325
333
|
listMythosActions?(): Promise<IMythosAction[]>;
|
|
334
|
+
/** Fire a Mythos external event with an optional payload. */
|
|
335
|
+
fireMythosEvent?(eventType: string, payload?: Record<string, unknown>): Promise<void>;
|
|
336
|
+
/** Build an IDataFunction over a Mythos collection for DataList/DataTable. */
|
|
337
|
+
fromMythosCollection?(model: string, collection: string): (
|
|
338
|
+
max: number,
|
|
339
|
+
lastPageToken: string,
|
|
340
|
+
args?: { search?: string } & Record<string, unknown>,
|
|
341
|
+
) => Promise<{ items: Array<Record<string, unknown>>; pageToken: string }>;
|
|
326
342
|
fireEvent(eventId: string): Promise<void>;
|
|
327
343
|
hasAppRole(app: string, role: string): boolean;
|
|
328
344
|
themeName?: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "infaira-canvas",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "InfAIra Canvas CLI — scaffold widgets that talk to ICan and Mythos.",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "InfAIra Canvas CLI — scaffold widgets that talk to ICan and Mythos (actions, events, public portals, paginated collections).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"infaira",
|
|
7
7
|
"ican",
|
|
@@ -38,8 +38,10 @@ In the portal, `icanContext` is always defined — the widget is never mounted u
|
|
|
38
38
|
| `language` | `string` | Active language code, e.g. `'en'` |
|
|
39
39
|
| `themeName` | `string \| undefined` | e.g. `'Dark'`, `'Glass Light'` |
|
|
40
40
|
| `themeType` | `'Dark' \| 'Light' \| 'Glass-Dark' \| 'Glass-Light' \| undefined` | Structured theme type for chart colours |
|
|
41
|
-
| `executeAction` | `(model, action, params?, options?) => Promise<unknown>` | Call an action. ICan native by default; prefix `model` with `mythos:` to route to the Mythos engine instead. |
|
|
41
|
+
| `executeAction` | `(model, action, params?, options?) => Promise<unknown>` | Call an action. ICan native by default; prefix `model` with `mythos:` to route to the Mythos engine instead. `options.cancelPrevious` + `options.key` to deduplicate live-filter calls. |
|
|
42
42
|
| `listMythosActions` | `() => Promise<IMythosAction[]> \| undefined` | List Mythos actions callable from this widget. Present only when the ICan backend has `MYTHOS_BASE_URL` configured. |
|
|
43
|
+
| `fireMythosEvent` | `(event_type, payload?) => Promise<void> \| undefined` | Fire a named external event into the Mythos bus. Triggers any matching Mythos event handlers. |
|
|
44
|
+
| `fromMythosCollection` | `(model, collection) => IDataFunction \| undefined` | Build a paginated data source over a Mythos collection — drops into DataList / DataTable. |
|
|
43
45
|
| `fireEvent` | `(eventId) => Promise<void>` | Trigger a portal event |
|
|
44
46
|
| `hasAppRole` | `(app, role) => boolean` | Check if the user has a role |
|
|
45
47
|
| `$L` | `(code, params?) => string` | Translate a localisation key |
|
|
@@ -125,6 +127,61 @@ const handleRun = async () => {
|
|
|
125
127
|
|
|
126
128
|
A working reference widget lives at `Widgets/ICan widgets/mythosdemo/` — pick an action, fill params, run, render the response.
|
|
127
129
|
|
|
130
|
+
### Cancelling stale calls (`options.cancelPrevious`)
|
|
131
|
+
|
|
132
|
+
Widgets that fire on every keystroke — search bars, live filters — end up racing requests against themselves. Use `cancelPrevious` + `key` to ensure only the latest call's result lands:
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
const onSearch = (text: string) => {
|
|
136
|
+
icanContext.executeAction(
|
|
137
|
+
'mythos:Inventory',
|
|
138
|
+
'Search',
|
|
139
|
+
{ q: text },
|
|
140
|
+
{ cancelPrevious: true, key: 'inventory-search' },
|
|
141
|
+
).then((res) => {
|
|
142
|
+
if (res !== null) setResults(res); // null means this call was cancelled
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Calls with the same `key` are tracked; when a newer one is issued, older ones resolve to `null` instead of delivering stale data into your state.
|
|
148
|
+
|
|
149
|
+
### Firing Mythos events
|
|
150
|
+
|
|
151
|
+
`icanContext.fireMythosEvent('order_placed', { id: 4821 })` publishes the event into Mythos's bus. Any Mythos workflow with a matching External Event trigger fires. Useful for widget → workflow dispatch where you don't need a return value.
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
await icanContext.fireMythosEvent?.('user_logged_in', { userId });
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Optional — `fireMythosEvent` is `undefined` when Mythos integration is disabled. Guard with `?.()`.
|
|
158
|
+
|
|
159
|
+
### Reading from a Mythos collection
|
|
160
|
+
|
|
161
|
+
`icanContext.fromMythosCollection(model, collection)` returns a function with the same shape ICan's DataList / DataTable already accept:
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
const fetchPage = icanContext.fromMythosCollection!('Inventory', 'items');
|
|
165
|
+
|
|
166
|
+
<DataList
|
|
167
|
+
dataFunction={fetchPage}
|
|
168
|
+
pageSize={50}
|
|
169
|
+
renderItem={(item) => <div>{item.sku} — {item.qty}</div>}
|
|
170
|
+
/>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The function takes `(max, lastPageToken, args?)` and returns `{ items, pageToken }`. Empty `pageToken` means "no more pages". Supports `args.search` for text filtering against the collection's searchable fields.
|
|
174
|
+
|
|
175
|
+
### Public portals (anonymous Mythos access)
|
|
176
|
+
|
|
177
|
+
When a widget runs on a `is_public: true` portal, the host calls `/api/public/execute-batch` instead of the authenticated endpoint. For `mythos:` calls in that batch:
|
|
178
|
+
|
|
179
|
+
- The Mythos action **must** have `no_auth: true` set in its API Route config
|
|
180
|
+
- The call is forwarded to Mythos's public route `/api/<scope>/models/{name}/actions/{name}` with no JWT
|
|
181
|
+
- Mythos enforces `no_auth` at its middleware layer — actions without that flag return `401` to the widget
|
|
182
|
+
|
|
183
|
+
No code change in the widget itself — the same `executeAction('mythos:X', 'Y', params)` call works in both authenticated and public portals.
|
|
184
|
+
|
|
128
185
|
---
|
|
129
186
|
|
|
130
187
|
## 3. Widget settings panel — `IWidgetPropConfig`
|
package/templates/ican.d.ts
CHANGED
|
@@ -714,11 +714,16 @@ declare module "ican/context" {
|
|
|
714
714
|
*
|
|
715
715
|
* Both call shapes return a Promise that resolves to the action's output
|
|
716
716
|
* envelope. For Mythos, the envelope is `{ run_id, status, output? }`.
|
|
717
|
+
*
|
|
718
|
+
* `options.cancelPrevious` + `options.key`: when an in-flight call shares
|
|
719
|
+
* the same key, its Promise resolves to `null` and the newer call wins.
|
|
720
|
+
* Designed for live-filter widgets where only the latest result matters.
|
|
717
721
|
*/
|
|
718
722
|
executeAction(
|
|
719
723
|
model: string,
|
|
720
724
|
action: string,
|
|
721
|
-
params?: Record<string, unknown
|
|
725
|
+
params?: Record<string, unknown>,
|
|
726
|
+
options?: { cancelPrevious?: boolean; key?: string }
|
|
722
727
|
): Promise<unknown>;
|
|
723
728
|
/**
|
|
724
729
|
* List every Mythos action published and callable from this widget.
|
|
@@ -727,6 +732,31 @@ declare module "ican/context" {
|
|
|
727
732
|
* once at mount and cache the result themselves.
|
|
728
733
|
*/
|
|
729
734
|
listMythosActions?: () => Promise<IMythosAction[]>;
|
|
735
|
+
/**
|
|
736
|
+
* Fire an external event into the Mythos engine. The event_type matches
|
|
737
|
+
* what Mythos event-handlers subscribe to (configured in the model's
|
|
738
|
+
* Events tab). Mirrors UXP's fireEvent, with an optional payload.
|
|
739
|
+
* Optional — present only when MYTHOS_BASE_URL is configured.
|
|
740
|
+
*/
|
|
741
|
+
fireMythosEvent?: (
|
|
742
|
+
eventType: string,
|
|
743
|
+
payload?: Record<string, unknown>
|
|
744
|
+
) => Promise<void>;
|
|
745
|
+
/**
|
|
746
|
+
* Build an IDataFunction over a Mythos collection — drop-in data source
|
|
747
|
+
* for DataList / DataTable. Mirrors UXP's fromLucyDataCollection.
|
|
748
|
+
* const fn = icanContext.fromMythosCollection('Inventory', 'items')
|
|
749
|
+
* const { items, pageToken } = await fn(50, '', { search: 'foo' })
|
|
750
|
+
* Optional — present only when MYTHOS_BASE_URL is configured.
|
|
751
|
+
*/
|
|
752
|
+
fromMythosCollection?: (
|
|
753
|
+
model: string,
|
|
754
|
+
collection: string
|
|
755
|
+
) => (
|
|
756
|
+
max: number,
|
|
757
|
+
lastPageToken: string,
|
|
758
|
+
args?: { search?: string } & Record<string, unknown>
|
|
759
|
+
) => Promise<{ items: Array<Record<string, unknown>>; pageToken: string }>;
|
|
730
760
|
/** Resolve a relative ICan backend path against the portal base URL. */
|
|
731
761
|
resolveUrl(path: string): string;
|
|
732
762
|
/** Logging helper namespaced to this widget instance. */
|
package/templates/index.html
CHANGED
|
@@ -1443,6 +1443,22 @@
|
|
|
1443
1443
|
{ model_id: 'dev-model-2', model_name: 'Buildings', action_id: 'dev-action-2', action_name: 'List', description: 'Dev stub' },
|
|
1444
1444
|
]);
|
|
1445
1445
|
},
|
|
1446
|
+
fireMythosEvent: function (eventType, payload) {
|
|
1447
|
+
console.log('[ICan Dev] fireMythosEvent:', eventType, payload);
|
|
1448
|
+
return Promise.resolve();
|
|
1449
|
+
},
|
|
1450
|
+
fromMythosCollection: function (model, collection) {
|
|
1451
|
+
return function (max, lastPageToken, args) {
|
|
1452
|
+
console.log('[ICan Dev] fromMythosCollection:', model, collection, { max: max, lastPageToken: lastPageToken, args: args });
|
|
1453
|
+
// Return one page of synthetic docs so DataList renders something.
|
|
1454
|
+
return Promise.resolve({
|
|
1455
|
+
items: Array.from({ length: Math.min(max, 5) }, function (_, i) {
|
|
1456
|
+
return { id: 'dev-' + i, name: model + ' row ' + i, value: i * 10 };
|
|
1457
|
+
}),
|
|
1458
|
+
pageToken: '',
|
|
1459
|
+
});
|
|
1460
|
+
};
|
|
1461
|
+
},
|
|
1446
1462
|
fireEvent: function (id) {
|
|
1447
1463
|
console.log('[ICan Dev] fireEvent:', id);
|
|
1448
1464
|
return Promise.resolve();
|
package/templates/ui.html
CHANGED
|
@@ -1142,6 +1142,21 @@
|
|
|
1142
1142
|
{ model_id: 'dev-model-2', model_name: 'Buildings', action_id: 'dev-action-2', action_name: 'List', description: 'Dev stub' },
|
|
1143
1143
|
]);
|
|
1144
1144
|
},
|
|
1145
|
+
fireMythosEvent: function (eventType, payload) {
|
|
1146
|
+
console.log('[ICan Dev] fireMythosEvent:', eventType, payload);
|
|
1147
|
+
return Promise.resolve();
|
|
1148
|
+
},
|
|
1149
|
+
fromMythosCollection: function (model, collection) {
|
|
1150
|
+
return function (max, lastPageToken, args) {
|
|
1151
|
+
console.log('[ICan Dev] fromMythosCollection:', model, collection, { max: max, lastPageToken: lastPageToken, args: args });
|
|
1152
|
+
return Promise.resolve({
|
|
1153
|
+
items: Array.from({ length: Math.min(max, 5) }, function (_, i) {
|
|
1154
|
+
return { id: 'dev-' + i, name: model + ' row ' + i, value: i * 10 };
|
|
1155
|
+
}),
|
|
1156
|
+
pageToken: '',
|
|
1157
|
+
});
|
|
1158
|
+
};
|
|
1159
|
+
},
|
|
1145
1160
|
fireEvent: function (id) {
|
|
1146
1161
|
console.log('[ICan Dev] fireEvent:', id);
|
|
1147
1162
|
return Promise.resolve();
|