@colixsystems/widget-sdk 0.7.0 → 0.8.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 +8 -3
- package/dist/contract.cjs +19 -1
- package/dist/contract.js +19 -1
- package/dist/hooks.js +74 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +1 -0
- package/dist/index.native.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,9 +6,14 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
6
6
|
|
|
7
7
|
## Status
|
|
8
8
|
|
|
9
|
-
`v0.
|
|
9
|
+
`v0.8.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**.
|
|
10
10
|
|
|
11
|
-
### What's new in 0.
|
|
11
|
+
### What's new in 0.8.0
|
|
12
|
+
|
|
13
|
+
- **`useDirectory()` — read-only user directory hook (REQ-DIR-01).** Returns `{ users, loading, error, refetch }` where each user is `{ id, name, role }`. Backed by a new `WidgetContext.directory.listUsers(query)` slice and gated by the new `directory.read:users` scope. Use it to build a chat people-list, an @-mention picker, or to resolve an author id to a display name. The host reads `GET /api/v1/app/users`, which hands non-Studio (Player) callers the reduced `{ id, name, role }` projection — email and other admin-only fields never leave the server for an app end-user. `query` is an optional `{ q, role, isActive, limit, offset }` (`q` substring-matches the display name; `role` is `"USER"` (default), `"INTEGRATION"`, or `"ALL"`). Mutating users is not part of the widget surface — the directory is read-only.
|
|
14
|
+
- **`CONTRACT.version` → `1.1.0`** (additive: one new hook, one new context slice, one new scope). No existing export changed signature.
|
|
15
|
+
|
|
16
|
+
### What was in 0.7.0
|
|
12
17
|
|
|
13
18
|
- **`datastoreTemplate` is now part of the public manifest contract.** `CONTRACT.manifestSchema` carries an optional `datastoreTemplate` entry alongside the existing fields. The TypeScript `WidgetManifest` already declared this since 0.3.0, but the runtime contract did not — the agent's system prompt only generated the field set advertised by `CONTRACT.manifestSchema`, which silently omitted `datastoreTemplate` from every AI-generated draft. The mismatch meant most AI-generated DATA widgets shipped without a seeded table, forcing the end-user to hand-build it before the widget would render anything useful. With the schema entry in place, the agent now defaults to including a `datastoreTemplate` whenever the widget reads or writes data, matching the no-code experience the platform promises.
|
|
14
19
|
- **Agent system prompt** ([backend/src/core/services/ai-widget-agent.service.js](../../backend/src/core/services/ai-widget-agent.service.js)) gains a dedicated `===== DATASTORE TEMPLATE =====` section, an updated DATA-widget example with a template, and a CONVERSATION BEHAVIOUR rule pinning the default. The note-list example (TextInput + create + update + delete) now shows the matching template too, including the column-name-match rule (`record.Body` ↔ `"name": "Body"`).
|
|
@@ -61,7 +66,7 @@ import { defineWidget, validateManifest, useDatastoreQuery, Text, View } from "@
|
|
|
61
66
|
|
|
62
67
|
- `defineWidget({ manifest, component })` — validates the manifest and produces a widget module the host can register.
|
|
63
68
|
- `validateManifest(m)` / `validatePropertySchema(s)` / `validateProps(schema, props)` — shape validation; no third-party deps.
|
|
64
|
-
- `useDatastoreQuery`, `useDatastoreMutation`, `useWidgetEvent`, `useTheme`, `useI18n` — hooks that read from the host-provided `WidgetContext`.
|
|
69
|
+
- `useDatastoreQuery`, `useDatastoreMutation`, `useDirectory`, `useWidgetEvent`, `useTheme`, `useI18n` — hooks that read from the host-provided `WidgetContext`. `useDirectory(query?)` returns `{ users, loading, error, refetch }` (each user `{ id, name, role }`) and requires the `directory.read:users` scope.
|
|
65
70
|
- `Text`, `View`, `Pressable`, `Image`, `ScrollView`, `TextInput`, `FlatList`, `SectionList`, `ActivityIndicator`, `Switch`, `StyleSheet` — re-exported from `react-native`. The web build aliases `react-native` to `react-native-web` so widgets render in the browser without any per-platform code; the exported Expo app's Metro bundler resolves the real `react-native` library. See https://reactnative.dev/docs/ for per-component props.
|
|
66
71
|
- `WidgetContextProvider` — React context provider that the host (Studio, Player, exported app) wraps widgets with.
|
|
67
72
|
|
package/dist/contract.cjs
CHANGED
|
@@ -78,6 +78,18 @@ const HOOKS = [
|
|
|
78
78
|
requiredContextSlice: ["datastore.records"],
|
|
79
79
|
scopes: ["datastore.write:*"],
|
|
80
80
|
},
|
|
81
|
+
{
|
|
82
|
+
name: "useDirectory",
|
|
83
|
+
signature: "useDirectory(query?)",
|
|
84
|
+
returnShape: {
|
|
85
|
+
users: "Array<{ id, name, role }>",
|
|
86
|
+
loading: "boolean",
|
|
87
|
+
error: "DatastoreError | null",
|
|
88
|
+
refetch: "() => Promise<void>",
|
|
89
|
+
},
|
|
90
|
+
requiredContextSlice: ["directory.listUsers"],
|
|
91
|
+
scopes: ["directory.read:users"],
|
|
92
|
+
},
|
|
81
93
|
{
|
|
82
94
|
name: "useWidgetEvent",
|
|
83
95
|
signature: "useWidgetEvent(eventName)",
|
|
@@ -312,6 +324,12 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
312
324
|
required: true,
|
|
313
325
|
fields: { records: "function" },
|
|
314
326
|
},
|
|
327
|
+
directory: {
|
|
328
|
+
description:
|
|
329
|
+
"Read-only user directory. { listUsers(query?) -> Promise<Array<{ id, name, role }>> }. Backs useDirectory(); for chat people-lists / @-mention pickers / author-id resolution. Requires the directory.read:users scope.",
|
|
330
|
+
required: true,
|
|
331
|
+
fields: { listUsers: "function" },
|
|
332
|
+
},
|
|
315
333
|
events: {
|
|
316
334
|
description: "{ emit(name, payload) }.",
|
|
317
335
|
required: true,
|
|
@@ -409,7 +427,7 @@ function deepFreeze(value) {
|
|
|
409
427
|
}
|
|
410
428
|
|
|
411
429
|
const CONTRACT = deepFreeze({
|
|
412
|
-
version: "1.
|
|
430
|
+
version: "1.1.0",
|
|
413
431
|
hooks: HOOKS,
|
|
414
432
|
primitives: PRIMITIVES,
|
|
415
433
|
manifestSchema: MANIFEST_SCHEMA,
|
package/dist/contract.js
CHANGED
|
@@ -79,6 +79,18 @@ const HOOKS = [
|
|
|
79
79
|
requiredContextSlice: ["datastore.records"],
|
|
80
80
|
scopes: ["datastore.write:*"],
|
|
81
81
|
},
|
|
82
|
+
{
|
|
83
|
+
name: "useDirectory",
|
|
84
|
+
signature: "useDirectory(query?)",
|
|
85
|
+
returnShape: {
|
|
86
|
+
users: "Array<{ id, name, role }>",
|
|
87
|
+
loading: "boolean",
|
|
88
|
+
error: "DatastoreError | null",
|
|
89
|
+
refetch: "() => Promise<void>",
|
|
90
|
+
},
|
|
91
|
+
requiredContextSlice: ["directory.listUsers"],
|
|
92
|
+
scopes: ["directory.read:users"],
|
|
93
|
+
},
|
|
82
94
|
{
|
|
83
95
|
name: "useWidgetEvent",
|
|
84
96
|
signature: "useWidgetEvent(eventName)",
|
|
@@ -306,6 +318,12 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
306
318
|
required: true,
|
|
307
319
|
fields: { records: "function" },
|
|
308
320
|
},
|
|
321
|
+
directory: {
|
|
322
|
+
description:
|
|
323
|
+
"Read-only user directory. { listUsers(query?) -> Promise<Array<{ id, name, role }>> }. Backs useDirectory(); for chat people-lists / @-mention pickers / author-id resolution. Requires the directory.read:users scope.",
|
|
324
|
+
required: true,
|
|
325
|
+
fields: { listUsers: "function" },
|
|
326
|
+
},
|
|
309
327
|
events: {
|
|
310
328
|
description: "{ emit(name, payload) }.",
|
|
311
329
|
required: true,
|
|
@@ -401,7 +419,7 @@ function deepFreeze(value) {
|
|
|
401
419
|
}
|
|
402
420
|
|
|
403
421
|
const CONTRACT = deepFreeze({
|
|
404
|
-
version: "1.
|
|
422
|
+
version: "1.1.0",
|
|
405
423
|
hooks: HOOKS,
|
|
406
424
|
primitives: PRIMITIVES,
|
|
407
425
|
manifestSchema: MANIFEST_SCHEMA,
|
package/dist/hooks.js
CHANGED
|
@@ -258,6 +258,80 @@ export function useDatastoreMutation(table) {
|
|
|
258
258
|
};
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Stateful user-directory query hook. Returns { users, loading, error,
|
|
263
|
+
* refetch }.
|
|
264
|
+
*
|
|
265
|
+
* The host's directory client exposes `listUsers(query)` which resolves
|
|
266
|
+
* to an array of `{ id, name, role }` rows — the privacy-reduced
|
|
267
|
+
* directory projection the backend hands to non-Studio (Player) callers.
|
|
268
|
+
* Use it to build a chat people-list, an @-mention picker, or to resolve
|
|
269
|
+
* an author id to a display name. Mutating users is NOT part of this
|
|
270
|
+
* surface; the directory is read-only from a widget.
|
|
271
|
+
*
|
|
272
|
+
* `query` is an optional `{ q?, role?, isActive?, limit?, offset? }`
|
|
273
|
+
* object. `q` substring-matches the display name; `role` is `"USER"`
|
|
274
|
+
* (default) or `"INTEGRATION"` or `"ALL"`. The hook re-fetches whenever
|
|
275
|
+
* `JSON.stringify(query)` changes and exposes `refetch` for on-demand
|
|
276
|
+
* reloads (e.g. a chat roster refresh).
|
|
277
|
+
*
|
|
278
|
+
* Requires the `directory.read:users` scope in the widget manifest's
|
|
279
|
+
* `requestedScopes`.
|
|
280
|
+
*/
|
|
281
|
+
export function useDirectory(query) {
|
|
282
|
+
const ctx = useWidgetContextOrThrow("useDirectory");
|
|
283
|
+
if (!ctx.directory || typeof ctx.directory.listUsers !== "function") {
|
|
284
|
+
throw new Error("useDirectory: host did not inject a directory client");
|
|
285
|
+
}
|
|
286
|
+
const [users, setUsers] = useState([]);
|
|
287
|
+
const [loading, setLoading] = useState(true);
|
|
288
|
+
const [error, setError] = useState(null);
|
|
289
|
+
|
|
290
|
+
// Same ref discipline as useDatastoreQuery: the host rebuilds the
|
|
291
|
+
// WidgetContext value every render, so we capture the live query +
|
|
292
|
+
// client in refs to keep `refetch` a stable identity.
|
|
293
|
+
const queryRef = useRef(query);
|
|
294
|
+
const listUsersRef = useRef(ctx.directory.listUsers);
|
|
295
|
+
queryRef.current = query;
|
|
296
|
+
listUsersRef.current = ctx.directory.listUsers;
|
|
297
|
+
|
|
298
|
+
const runRef = useRef(0);
|
|
299
|
+
|
|
300
|
+
const doFetch = useCallback(async () => {
|
|
301
|
+
const myRun = ++runRef.current;
|
|
302
|
+
setLoading(true);
|
|
303
|
+
setError(null);
|
|
304
|
+
try {
|
|
305
|
+
const rows = await listUsersRef.current(queryRef.current);
|
|
306
|
+
if (runRef.current !== myRun) return;
|
|
307
|
+
setUsers(Array.isArray(rows) ? rows : []);
|
|
308
|
+
setLoading(false);
|
|
309
|
+
} catch (err) {
|
|
310
|
+
if (runRef.current !== myRun) return;
|
|
311
|
+
setError(toDatastoreError(err));
|
|
312
|
+
setLoading(false);
|
|
313
|
+
}
|
|
314
|
+
}, []);
|
|
315
|
+
|
|
316
|
+
const queryKey = (() => {
|
|
317
|
+
try {
|
|
318
|
+
return JSON.stringify(query);
|
|
319
|
+
} catch (_e) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
})();
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
doFetch();
|
|
325
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
326
|
+
}, [queryKey]);
|
|
327
|
+
|
|
328
|
+
const refetch = useCallback(async () => {
|
|
329
|
+
await doFetch();
|
|
330
|
+
}, [doFetch]);
|
|
331
|
+
|
|
332
|
+
return { users, loading, error, refetch };
|
|
333
|
+
}
|
|
334
|
+
|
|
261
335
|
/**
|
|
262
336
|
* Emit a named widget event through ctx.events.emit. Page-level event
|
|
263
337
|
* bindings (subscribed by the host) decide what happens next.
|
package/dist/index.d.ts
CHANGED
|
@@ -184,6 +184,9 @@ export interface WidgetContext<TProps = unknown> {
|
|
|
184
184
|
currentRoute: { pageId: string; params: Record<string, string> };
|
|
185
185
|
};
|
|
186
186
|
datastore: unknown; // typed by @colixsystems/datastore-client
|
|
187
|
+
directory: {
|
|
188
|
+
listUsers(query?: DirectoryQuery): Promise<DirectoryUser[]>;
|
|
189
|
+
};
|
|
187
190
|
events: { emit(eventName: string, payload?: unknown): void };
|
|
188
191
|
i18n: {
|
|
189
192
|
locale: string;
|
|
@@ -274,6 +277,43 @@ export function useDatastoreMutation<T = unknown>(
|
|
|
274
277
|
table: string,
|
|
275
278
|
): MutationApi<T>;
|
|
276
279
|
|
|
280
|
+
/**
|
|
281
|
+
* A single row from the read-only user directory. `role` is `"USER"`
|
|
282
|
+
* for a human end-user or `"INTEGRATION"` for a service account. The
|
|
283
|
+
* directory deliberately omits email and other admin-only fields.
|
|
284
|
+
*/
|
|
285
|
+
export interface DirectoryUser {
|
|
286
|
+
id: string;
|
|
287
|
+
name: string;
|
|
288
|
+
role: "USER" | "INTEGRATION";
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export interface DirectoryQuery {
|
|
292
|
+
/** Case-insensitive substring match on the display name. */
|
|
293
|
+
q?: string;
|
|
294
|
+
/** `"USER"` (default), `"INTEGRATION"`, or `"ALL"`. */
|
|
295
|
+
role?: "USER" | "INTEGRATION" | "ALL";
|
|
296
|
+
/** Filter by active state. */
|
|
297
|
+
isActive?: boolean;
|
|
298
|
+
limit?: number;
|
|
299
|
+
offset?: number;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export interface DirectoryResult {
|
|
303
|
+
users: DirectoryUser[];
|
|
304
|
+
loading: boolean;
|
|
305
|
+
error: DatastoreError | null;
|
|
306
|
+
refetch(): Promise<void>;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Read-only user directory hook. Resolves the tenant's app users to
|
|
311
|
+
* `{ id, name, role }` rows for chat people-lists, @-mention pickers, or
|
|
312
|
+
* author-id → display-name resolution. Requires the
|
|
313
|
+
* `directory.read:users` scope in the widget manifest.
|
|
314
|
+
*/
|
|
315
|
+
export function useDirectory(query?: DirectoryQuery): DirectoryResult;
|
|
316
|
+
|
|
277
317
|
export function useWidgetEvent(name: string): (payload?: unknown) => void;
|
|
278
318
|
|
|
279
319
|
export function useTheme(): ThemeTokens;
|
package/dist/index.js
CHANGED
package/dist/index.native.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|