@colixsystems/widget-sdk 0.7.0 → 0.9.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 +13 -3
- package/dist/contract.cjs +37 -1
- package/dist/contract.js +37 -1
- package/dist/hooks.js +180 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +3 -0
- package/dist/index.native.js +3 -0
- package/dist/primitives.js +38 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,9 +6,19 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
6
6
|
|
|
7
7
|
## Status
|
|
8
8
|
|
|
9
|
-
`v0.
|
|
9
|
+
`v0.9.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.9.0
|
|
12
|
+
|
|
13
|
+
- **`usePayments()` — incoming app-user payments (REQ-BILL-07-WIDGETPAY).** Returns `{ requestPayment, getPayment }`. `requestPayment({ amountCents, currency?, description, metadata?, returnPath? })` triggers a one-time charge from the signed-in app user and resolves to `{ id, status, checkoutUrl? }`: when `checkoutUrl` is present the widget opens it (web: navigate; native: `expo-web-browser`) — the user pays in **hosted Stripe Checkout**; when absent (the platform's built-in **mock** provider, the default until Stripe is configured) the charge auto-confirms (`status: "PAID"`). `getPayment(id)` polls the terminal status. Backed by a new `WidgetContext.payments` slice and gated by the new `payments.charge:appUser` scope. **No card data ever touches the widget** — never collect card fields yourself. The charge settles to the workspace owner; the amount is bounded by a platform per-charge cap. Rejections are a structured `PaymentError` (also a new named export) with a stable `.code`.
|
|
14
|
+
- **`CONTRACT.version` → `1.2.0`** (additive: one new hook, one new context slice, one new scope, one new error class). No existing export changed signature.
|
|
15
|
+
|
|
16
|
+
### What was in 0.8.0
|
|
17
|
+
|
|
18
|
+
- **`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.
|
|
19
|
+
- **`CONTRACT.version` → `1.1.0`** (additive: one new hook, one new context slice, one new scope). No existing export changed signature.
|
|
20
|
+
|
|
21
|
+
### What was in 0.7.0
|
|
12
22
|
|
|
13
23
|
- **`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
24
|
- **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 +71,7 @@ import { defineWidget, validateManifest, useDatastoreQuery, Text, View } from "@
|
|
|
61
71
|
|
|
62
72
|
- `defineWidget({ manifest, component })` — validates the manifest and produces a widget module the host can register.
|
|
63
73
|
- `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`.
|
|
74
|
+
- `useDatastoreQuery`, `useDatastoreMutation`, `useDirectory`, `useWidgetEvent`, `usePayments`, `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. `usePayments()` returns `{ requestPayment, getPayment }` and requires the `payments.charge:appUser` scope; `requestPayment(...)` rejects with a `PaymentError`.
|
|
65
75
|
- `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
76
|
- `WidgetContextProvider` — React context provider that the host (Studio, Player, exported app) wraps widgets with.
|
|
67
77
|
|
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)",
|
|
@@ -85,6 +97,18 @@ const HOOKS = [
|
|
|
85
97
|
requiredContextSlice: ["events.emit"],
|
|
86
98
|
scopes: null,
|
|
87
99
|
},
|
|
100
|
+
{
|
|
101
|
+
name: "usePayments",
|
|
102
|
+
signature: "usePayments()",
|
|
103
|
+
returnShape: {
|
|
104
|
+
requestPayment:
|
|
105
|
+
"({ amountCents, currency?, description, metadata? }) => Promise<{ id, status, checkoutUrl? }> // rejects with PaymentError",
|
|
106
|
+
getPayment:
|
|
107
|
+
"(paymentId) => Promise<{ id, status, amountCents, currency, description }>",
|
|
108
|
+
},
|
|
109
|
+
requiredContextSlice: ["payments.requestPayment"],
|
|
110
|
+
scopes: ["payments.charge:appUser"],
|
|
111
|
+
},
|
|
88
112
|
];
|
|
89
113
|
|
|
90
114
|
// REQ-WSDK-RN-WEB: the SDK exposes the React Native primitive API
|
|
@@ -312,11 +336,23 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
312
336
|
required: true,
|
|
313
337
|
fields: { records: "function" },
|
|
314
338
|
},
|
|
339
|
+
directory: {
|
|
340
|
+
description:
|
|
341
|
+
"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.",
|
|
342
|
+
required: true,
|
|
343
|
+
fields: { listUsers: "function" },
|
|
344
|
+
},
|
|
315
345
|
events: {
|
|
316
346
|
description: "{ emit(name, payload) }.",
|
|
317
347
|
required: true,
|
|
318
348
|
fields: { emit: "function" },
|
|
319
349
|
},
|
|
350
|
+
payments: {
|
|
351
|
+
description:
|
|
352
|
+
"Incoming app-user payments (REQ-BILL-07-WIDGETPAY). { requestPayment({ amountCents, currency?, description, metadata? }) -> Promise<{ id, status, checkoutUrl? }>, getPayment(id) -> Promise<payment> }. Backs usePayments(); requires the payments.charge:appUser scope. The host opens hosted Checkout (or auto-confirms under the mock provider); the charge settles to the workspace owner.",
|
|
353
|
+
required: true,
|
|
354
|
+
fields: { requestPayment: "function", getPayment: "function" },
|
|
355
|
+
},
|
|
320
356
|
i18n: {
|
|
321
357
|
description: "{ t(key, fallback?), locale }.",
|
|
322
358
|
required: true,
|
|
@@ -409,7 +445,7 @@ function deepFreeze(value) {
|
|
|
409
445
|
}
|
|
410
446
|
|
|
411
447
|
const CONTRACT = deepFreeze({
|
|
412
|
-
version: "1.
|
|
448
|
+
version: "1.2.0",
|
|
413
449
|
hooks: HOOKS,
|
|
414
450
|
primitives: PRIMITIVES,
|
|
415
451
|
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)",
|
|
@@ -86,6 +98,18 @@ const HOOKS = [
|
|
|
86
98
|
requiredContextSlice: ["events.emit"],
|
|
87
99
|
scopes: null,
|
|
88
100
|
},
|
|
101
|
+
{
|
|
102
|
+
name: "usePayments",
|
|
103
|
+
signature: "usePayments()",
|
|
104
|
+
returnShape: {
|
|
105
|
+
requestPayment:
|
|
106
|
+
"({ amountCents, currency?, description, metadata? }) => Promise<{ id, status, checkoutUrl? }> // rejects with PaymentError",
|
|
107
|
+
getPayment:
|
|
108
|
+
"(paymentId) => Promise<{ id, status, amountCents, currency, description }>",
|
|
109
|
+
},
|
|
110
|
+
requiredContextSlice: ["payments.requestPayment"],
|
|
111
|
+
scopes: ["payments.charge:appUser"],
|
|
112
|
+
},
|
|
89
113
|
];
|
|
90
114
|
|
|
91
115
|
// REQ-WSDK-RN-WEB: see contract.cjs for the source-of-truth comment.
|
|
@@ -306,11 +330,23 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
306
330
|
required: true,
|
|
307
331
|
fields: { records: "function" },
|
|
308
332
|
},
|
|
333
|
+
directory: {
|
|
334
|
+
description:
|
|
335
|
+
"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.",
|
|
336
|
+
required: true,
|
|
337
|
+
fields: { listUsers: "function" },
|
|
338
|
+
},
|
|
309
339
|
events: {
|
|
310
340
|
description: "{ emit(name, payload) }.",
|
|
311
341
|
required: true,
|
|
312
342
|
fields: { emit: "function" },
|
|
313
343
|
},
|
|
344
|
+
payments: {
|
|
345
|
+
description:
|
|
346
|
+
"Incoming app-user payments (REQ-BILL-07-WIDGETPAY). { requestPayment({ amountCents, currency?, description, metadata? }) -> Promise<{ id, status, checkoutUrl? }>, getPayment(id) -> Promise<payment> }. Backs usePayments(); requires the payments.charge:appUser scope. The host opens hosted Checkout (or auto-confirms under the mock provider); the charge settles to the workspace owner.",
|
|
347
|
+
required: true,
|
|
348
|
+
fields: { requestPayment: "function", getPayment: "function" },
|
|
349
|
+
},
|
|
314
350
|
i18n: {
|
|
315
351
|
description: "{ t(key, fallback?), locale }.",
|
|
316
352
|
required: true,
|
|
@@ -401,7 +437,7 @@ function deepFreeze(value) {
|
|
|
401
437
|
}
|
|
402
438
|
|
|
403
439
|
const CONTRACT = deepFreeze({
|
|
404
|
-
version: "1.
|
|
440
|
+
version: "1.2.0",
|
|
405
441
|
hooks: HOOKS,
|
|
406
442
|
primitives: PRIMITIVES,
|
|
407
443
|
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.
|
|
@@ -278,6 +352,112 @@ export function useTheme() {
|
|
|
278
352
|
return ctx.workspace.theme;
|
|
279
353
|
}
|
|
280
354
|
|
|
355
|
+
/**
|
|
356
|
+
* Structured error thrown by `usePayments` callbacks. Carries a stable
|
|
357
|
+
* `code` so widgets can branch without parsing message strings.
|
|
358
|
+
*
|
|
359
|
+
* `code` is one of:
|
|
360
|
+
* - "AUTH_REQUIRED" — no signed-in app user
|
|
361
|
+
* - "PAYMENTS_SCOPE_NOT_GRANTED"— widget lacks payments.charge:appUser
|
|
362
|
+
* - "INVALID_AMOUNT" / "AMOUNT_TOO_LARGE" / "VALIDATION" — bad request
|
|
363
|
+
* - "CONNECT_NOT_READY" / "PAYMENTS_DISABLED" — provider not ready
|
|
364
|
+
* - "DECLINED" — the charge was declined
|
|
365
|
+
* - "INTERNAL" — anything else
|
|
366
|
+
*/
|
|
367
|
+
export class PaymentError extends Error {
|
|
368
|
+
constructor(code, message, opts) {
|
|
369
|
+
super(message);
|
|
370
|
+
this.name = "PaymentError";
|
|
371
|
+
this.code = code;
|
|
372
|
+
if (opts && opts.cause) this.cause = opts.cause;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function toPaymentError(err) {
|
|
377
|
+
if (err instanceof PaymentError) return err;
|
|
378
|
+
const status =
|
|
379
|
+
err && err.response && typeof err.response.status === "number"
|
|
380
|
+
? err.response.status
|
|
381
|
+
: null;
|
|
382
|
+
const bodyCode =
|
|
383
|
+
err && err.response && err.response.data && err.response.data.code;
|
|
384
|
+
const bodyMessage =
|
|
385
|
+
err && err.response && err.response.data && err.response.data.error;
|
|
386
|
+
let code = "INTERNAL";
|
|
387
|
+
if (typeof bodyCode === "string" && bodyCode) code = bodyCode;
|
|
388
|
+
else if (status === 401) code = "AUTH_REQUIRED";
|
|
389
|
+
else if (status === 402) code = "DECLINED";
|
|
390
|
+
else if (status === 403) code = "FORBIDDEN";
|
|
391
|
+
else if (status === 400) code = "VALIDATION";
|
|
392
|
+
const message =
|
|
393
|
+
(typeof bodyMessage === "string" && bodyMessage) ||
|
|
394
|
+
(err && typeof err.message === "string"
|
|
395
|
+
? err.message
|
|
396
|
+
: "Payment request failed");
|
|
397
|
+
return new PaymentError(code, message, { cause: err });
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Incoming app-user payments (REQ-BILL-07-WIDGETPAY). Returns
|
|
402
|
+
* `{ requestPayment, getPayment }`.
|
|
403
|
+
*
|
|
404
|
+
* requestPayment({ amountCents, currency?, description, metadata? })
|
|
405
|
+
* → Promise<{ id, status, checkoutUrl?, ... }>. The host either
|
|
406
|
+
* auto-confirms (mock provider, `status: "PAID"`, no redirect) or
|
|
407
|
+
* returns a hosted-Checkout `checkoutUrl` the widget should open
|
|
408
|
+
* (Stripe provider, `status: "PENDING"`). Rejects with a
|
|
409
|
+
* `PaymentError`.
|
|
410
|
+
* getPayment(paymentId) → Promise<payment> — poll the terminal status.
|
|
411
|
+
*
|
|
412
|
+
* Requires the `payments.charge:appUser` scope in the manifest's
|
|
413
|
+
* `requestedScopes`. The charge settles to the workspace owner; the app
|
|
414
|
+
* user confirms the amount in hosted Checkout. No card data touches the
|
|
415
|
+
* widget — never collect card fields yourself.
|
|
416
|
+
*/
|
|
417
|
+
export function usePayments() {
|
|
418
|
+
const ctx = useWidgetContextOrThrow("usePayments");
|
|
419
|
+
if (!ctx.payments || typeof ctx.payments.requestPayment !== "function") {
|
|
420
|
+
throw new Error(
|
|
421
|
+
"usePayments: host did not inject a payments client. The widget must " +
|
|
422
|
+
"declare the payments.charge:appUser scope and be installed in a " +
|
|
423
|
+
"workspace whose host supports payments.",
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
const requestRef = useRef(ctx.payments.requestPayment);
|
|
427
|
+
const getRef = useRef(
|
|
428
|
+
typeof ctx.payments.getPayment === "function"
|
|
429
|
+
? ctx.payments.getPayment
|
|
430
|
+
: null,
|
|
431
|
+
);
|
|
432
|
+
requestRef.current = ctx.payments.requestPayment;
|
|
433
|
+
getRef.current =
|
|
434
|
+
typeof ctx.payments.getPayment === "function"
|
|
435
|
+
? ctx.payments.getPayment
|
|
436
|
+
: null;
|
|
437
|
+
|
|
438
|
+
const requestPayment = useCallback(async (args) => {
|
|
439
|
+
try {
|
|
440
|
+
return await requestRef.current(args);
|
|
441
|
+
} catch (err) {
|
|
442
|
+
throw toPaymentError(err);
|
|
443
|
+
}
|
|
444
|
+
}, []);
|
|
445
|
+
const getPayment = useCallback(async (paymentId) => {
|
|
446
|
+
if (!getRef.current) {
|
|
447
|
+
throw new PaymentError(
|
|
448
|
+
"INTERNAL",
|
|
449
|
+
"getPayment is not supported by this host.",
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
try {
|
|
453
|
+
return await getRef.current(paymentId);
|
|
454
|
+
} catch (err) {
|
|
455
|
+
throw toPaymentError(err);
|
|
456
|
+
}
|
|
457
|
+
}, []);
|
|
458
|
+
return { requestPayment, getPayment };
|
|
459
|
+
}
|
|
460
|
+
|
|
281
461
|
/**
|
|
282
462
|
* Returns { t, locale }. `t(key, fallback)` resolves `{{t:key}}` against
|
|
283
463
|
* the host's translation table and falls back to `fallback ?? key` when
|
package/dist/index.d.ts
CHANGED
|
@@ -184,7 +184,14 @@ 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 };
|
|
191
|
+
payments: {
|
|
192
|
+
requestPayment(args: PaymentRequest): Promise<PaymentResult>;
|
|
193
|
+
getPayment(paymentId: string): Promise<PaymentResult>;
|
|
194
|
+
};
|
|
188
195
|
i18n: {
|
|
189
196
|
locale: string;
|
|
190
197
|
t(key: string, vars?: Record<string, unknown>): string;
|
|
@@ -274,8 +281,86 @@ export function useDatastoreMutation<T = unknown>(
|
|
|
274
281
|
table: string,
|
|
275
282
|
): MutationApi<T>;
|
|
276
283
|
|
|
284
|
+
/**
|
|
285
|
+
* A single row from the read-only user directory. `role` is `"USER"`
|
|
286
|
+
* for a human end-user or `"INTEGRATION"` for a service account. The
|
|
287
|
+
* directory deliberately omits email and other admin-only fields.
|
|
288
|
+
*/
|
|
289
|
+
export interface DirectoryUser {
|
|
290
|
+
id: string;
|
|
291
|
+
name: string;
|
|
292
|
+
role: "USER" | "INTEGRATION";
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export interface DirectoryQuery {
|
|
296
|
+
/** Case-insensitive substring match on the display name. */
|
|
297
|
+
q?: string;
|
|
298
|
+
/** `"USER"` (default), `"INTEGRATION"`, or `"ALL"`. */
|
|
299
|
+
role?: "USER" | "INTEGRATION" | "ALL";
|
|
300
|
+
/** Filter by active state. */
|
|
301
|
+
isActive?: boolean;
|
|
302
|
+
limit?: number;
|
|
303
|
+
offset?: number;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export interface DirectoryResult {
|
|
307
|
+
users: DirectoryUser[];
|
|
308
|
+
loading: boolean;
|
|
309
|
+
error: DatastoreError | null;
|
|
310
|
+
refetch(): Promise<void>;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Read-only user directory hook. Resolves the tenant's app users to
|
|
315
|
+
* `{ id, name, role }` rows for chat people-lists, @-mention pickers, or
|
|
316
|
+
* author-id → display-name resolution. Requires the
|
|
317
|
+
* `directory.read:users` scope in the widget manifest.
|
|
318
|
+
*/
|
|
319
|
+
export function useDirectory(query?: DirectoryQuery): DirectoryResult;
|
|
320
|
+
|
|
277
321
|
export function useWidgetEvent(name: string): (payload?: unknown) => void;
|
|
278
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Arguments for `usePayments().requestPayment(...)`. `amountCents` is the
|
|
325
|
+
* charge in the currency's minor unit; the app user confirms it in hosted
|
|
326
|
+
* Checkout. `metadata` is an optional flat map carried through for the
|
|
327
|
+
* widget's own reconciliation (never used to derive the amount).
|
|
328
|
+
*/
|
|
329
|
+
export interface PaymentRequest {
|
|
330
|
+
amountCents: number;
|
|
331
|
+
currency?: string;
|
|
332
|
+
description: string;
|
|
333
|
+
metadata?: Record<string, string>;
|
|
334
|
+
/** Site-relative path to return to after Checkout (e.g. "/cart"). */
|
|
335
|
+
returnPath?: string;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export interface PaymentResult {
|
|
339
|
+
id: string;
|
|
340
|
+
status: "PENDING" | "PAID" | "FAILED" | "REFUNDED" | "CANCELLED";
|
|
341
|
+
amountCents?: number;
|
|
342
|
+
currency?: string;
|
|
343
|
+
description?: string;
|
|
344
|
+
/**
|
|
345
|
+
* Present (Stripe provider) when the app user must complete a hosted
|
|
346
|
+
* Checkout: the widget should open this URL. Absent under the mock
|
|
347
|
+
* provider, where the charge auto-confirms (`status: "PAID"`).
|
|
348
|
+
*/
|
|
349
|
+
checkoutUrl?: string | null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export interface PaymentsApi {
|
|
353
|
+
requestPayment(args: PaymentRequest): Promise<PaymentResult>;
|
|
354
|
+
getPayment(paymentId: string): Promise<PaymentResult>;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Incoming app-user payments (REQ-BILL-07-WIDGETPAY). Requires the
|
|
359
|
+
* `payments.charge:appUser` scope in the widget manifest. The charge
|
|
360
|
+
* settles to the workspace owner; no card data touches the widget.
|
|
361
|
+
*/
|
|
362
|
+
export function usePayments(): PaymentsApi;
|
|
363
|
+
|
|
279
364
|
export function useTheme(): ThemeTokens;
|
|
280
365
|
|
|
281
366
|
export function useI18n(): {
|
package/dist/index.js
CHANGED
|
@@ -8,9 +8,12 @@ export { validatePropertySchema, validateProps } from "./property-schema.js";
|
|
|
8
8
|
export {
|
|
9
9
|
WidgetContextProvider,
|
|
10
10
|
DatastoreError,
|
|
11
|
+
PaymentError,
|
|
11
12
|
useDatastoreQuery,
|
|
12
13
|
useDatastoreMutation,
|
|
14
|
+
useDirectory,
|
|
13
15
|
useWidgetEvent,
|
|
16
|
+
usePayments,
|
|
14
17
|
useTheme,
|
|
15
18
|
useI18n,
|
|
16
19
|
} from "./hooks.js";
|
package/dist/index.native.js
CHANGED
|
@@ -8,9 +8,12 @@ export { validatePropertySchema, validateProps } from "./property-schema.js";
|
|
|
8
8
|
export {
|
|
9
9
|
WidgetContextProvider,
|
|
10
10
|
DatastoreError,
|
|
11
|
+
PaymentError,
|
|
11
12
|
useDatastoreQuery,
|
|
12
13
|
useDatastoreMutation,
|
|
14
|
+
useDirectory,
|
|
13
15
|
useWidgetEvent,
|
|
16
|
+
usePayments,
|
|
14
17
|
useTheme,
|
|
15
18
|
useI18n,
|
|
16
19
|
} from "./hooks.js";
|
package/dist/primitives.js
CHANGED
|
@@ -17,19 +17,41 @@
|
|
|
17
17
|
// The static analyzer's allowedBareImports still rejects direct
|
|
18
18
|
// `react-native` imports; the SDK is the single entry point.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
} from
|
|
20
|
+
// This is the WEB primitive surface (the native shell uses
|
|
21
|
+
// `primitives.native.js`, which Metro selects via the package.json
|
|
22
|
+
// `react-native` field). It imports `react-native-web` directly rather
|
|
23
|
+
// than the bare `react-native` specifier for two reasons:
|
|
24
|
+
//
|
|
25
|
+
// 1. `react-native` is declared an OPTIONAL peer dep of this package.
|
|
26
|
+
// When the host builds with Vite 8 / rolldown, rolldown intercepts
|
|
27
|
+
// the bare `react-native` import with a generated
|
|
28
|
+
// `__vite-optional-peer-dep` stub BEFORE the host's
|
|
29
|
+
// `react-native$ → react-native-web` alias can apply, leaving some
|
|
30
|
+
// members (`Switch`, `StyleSheet`, …) `undefined` at runtime.
|
|
31
|
+
// 2. The direct `export { X } from "react-native"` re-export form
|
|
32
|
+
// additionally failed the production build outright
|
|
33
|
+
// (`[MISSING_EXPORT] "Pressable" is not exported`) because rolldown
|
|
34
|
+
// does not statically trace react-native-web's
|
|
35
|
+
// `export { default as X } from './exports/X'` chains.
|
|
36
|
+
//
|
|
37
|
+
// Importing `react-native-web` as a namespace and re-exporting each
|
|
38
|
+
// member as a local `const` resolves both: react-native-web is a real
|
|
39
|
+
// installed dep (no stub), and runtime property access sidesteps the
|
|
40
|
+
// static re-export tracing. Behaviour is identical to the old
|
|
41
|
+
// `react-native` alias path on the web.
|
|
42
|
+
import * as ReactNative from "react-native-web";
|
|
43
|
+
|
|
44
|
+
export const Text = ReactNative.Text;
|
|
45
|
+
export const View = ReactNative.View;
|
|
46
|
+
export const Pressable = ReactNative.Pressable;
|
|
47
|
+
export const Image = ReactNative.Image;
|
|
48
|
+
export const ScrollView = ReactNative.ScrollView;
|
|
49
|
+
export const TextInput = ReactNative.TextInput;
|
|
50
|
+
// Additional cross-platform primitives the AI agent + handwritten
|
|
51
|
+
// widgets commonly reach for. All work in both react-native-web and
|
|
52
|
+
// native react-native without per-platform code.
|
|
53
|
+
export const FlatList = ReactNative.FlatList;
|
|
54
|
+
export const SectionList = ReactNative.SectionList;
|
|
55
|
+
export const ActivityIndicator = ReactNative.ActivityIndicator;
|
|
56
|
+
export const Switch = ReactNative.Switch;
|
|
57
|
+
export const StyleSheet = ReactNative.StyleSheet;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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",
|