@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 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.7.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**.
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.7.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.0.0",
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.0.0",
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";
@@ -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";
@@ -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
- export {
21
- Text,
22
- View,
23
- Pressable,
24
- Image,
25
- ScrollView,
26
- TextInput,
27
- // Additional cross-platform primitives the AI agent + handwritten
28
- // widgets commonly reach for. All work in both react-native-web and
29
- // native react-native without per-platform code.
30
- FlatList,
31
- SectionList,
32
- ActivityIndicator,
33
- Switch,
34
- StyleSheet,
35
- } from "react-native";
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.7.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",