@colixsystems/widget-sdk 0.180.1 → 0.182.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
@@ -59,7 +59,7 @@ The data layer lives in **four separate domain-client packages**, each instantia
59
59
  | **DIRECTORY** (`ctx.directory`) | `useDirectory(query)` (optional: query) | `{ users, loading, error, refetch }` | `directory.users.list` — `directory.read:users` |
60
60
  | **DIRECTORY** | `useUsers(query)` (optional: query) | `{ users, loading, error, refetch, invite, deactivate, reactivate, remove, sendPasswordReset }` | `directory.users.*` — `users.read:*` plus the `users.read` (or `users.write`) capability, else `FORBIDDEN` (edits, incl. `sendPasswordReset()`, also `users.write:*`; `remove()` also `users.delete:*`) |
61
61
  | **DIRECTORY** | `useGroups(query)` (optional: query) | `{ groups, loading, error, refetch, create, remove, addMember, removeMember }` | `directory.groups.*` — `groups.read:*` (mutations also `groups.write:*`) |
62
- | **DIRECTORY** | `useInvites(query)` (optional: query) | `{ invites, loading, error, refetch, resend, revoke }` | `directory.invites.*` — `users.write:*` + the SystemAcl `users.write` capability (the whole invite surface, list included). `query` is `{ status?, limit?, offset? }` with `status` ∈ `pending \| accepted \| revoked \| expired \| all` (endpoint default `all`). |
62
+ | **DIRECTORY** | `useInvites(query)` (optional: query) | `{ invites, loading, error, refetch, resend, revoke }` | `directory.invites.*` — `users.write:*` + the SystemAcl `users.write` capability (the whole invite surface, list included). `query` is `{ status?, q?, app_user_id?, limit?, offset? }` with `status` ∈ `pending \| accepted \| revoked \| expired \| open \| all` (endpoint default `all`; `open` = `pending` or `expired`); `q` matches the invite's name or email, `app_user_id` returns the invites reserving that user id. |
63
63
  | **DIRECTORY** | `useBankIdLink()` | `{ linked, available, status, qr, message, startLink, refresh, cancel, unlink, refetchStatus, … }` | `directory.bankid.*` — no scope (JWT-gated self-service) |
64
64
  | **FILESTORE** (`ctx.filestore`) | `usePdfExport({ spaceType, folderId })` (optional: spaceType, folderId) | `{ openPdf, opening, canOpenPdf, exportToPdf, exporting, error, lastExported }` | `ctx.filestore.files.renderPdf` + `ctx.device.pdfViewer`, `ctx.filestore.files.exportPdf` — `files.write:*`. `openPdf(html, { fileName? })` renders the HTML to a PDF server-side and OPENS it (web: PDF tab; native: print / share sheet) without saving. `exportToPdf(html, { fileName?, folderId? })` renders it and saves it as a file (`application/pdf`). Same server-side renderer on web + native. |
65
65
  | **PAYMENTS** (`ctx.payments`) | `usePayments()` | `{ requestPayment, getPayment }` | `ctx.payments.*` — `payments.charge:appUser`. Rejects with `PaymentError { code, message, retryable }`; when `retryable` is `false` show `message` and drop the retry. Charges are accepted ONLY in the currency the workspace sells in — omit `currency` and the platform applies it (a disagreeing literal is a publish-blocking `payment-currency` finding). |
@@ -74,7 +74,22 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
74
74
 
75
75
  ## Status
76
76
 
77
- `v0.180.1` — 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**.
77
+ `v0.182.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**.
78
+
79
+ ### What's new in 0.182.0 (contract 1.143.0)
80
+
81
+ **A `datastoreTemplate` relation decides what happens when the row it points at is deleted.** A `ONE_TO_ONE` / `ONE_TO_MANY` RELATION column may declare `onDelete`. With `"SET_NULL"`, the default, the row is kept and its reference is emptied. With `"CASCADE"`, the row is deleted along with the row it points at, and anything that cascades from it is deleted in turn. A `MANY_TO_MANY` column takes no `onDelete`: deleting a row on either side only removes its links. The platform applies the rule on every delete, so a widget that deletes a parent row never has to delete or clear its children itself. Use `CASCADE` for rows that mean nothing without their parent (order lines, a post's comments). Additive: a template without it gets `SET_NULL`.
82
+
83
+ ### What's new in 0.181.0 (contract 1.142.0)
84
+
85
+ **A `USER` column can point at someone who has been invited but has not joined yet.** Every invite now reserves the id its invitee's user account will have, so a record can be assigned to that person straight away and nothing needs rewriting when they accept.
86
+
87
+ - **`useInvites()` rows carry `app_user_id`** — that reserved id. The `resend()` result carries it too.
88
+ - **`useInvites(query)` takes two more optional keys.** `q` is a case-insensitive substring match on the invite's name or email. `app_user_id` returns only the invites reserving that id, which is how a member-admin widget resolves the person behind a `USER` value before they have joined; an id no invite reserves returns no rows.
89
+ - **`status: 'open'`** is every invite that is neither accepted nor revoked — `pending` or `expired`. A re-invite keeps the reserved id, so `useInvites({ app_user_id, status: 'open' })` is the one invite that still reserves it.
90
+ - **A `USER` column has a third read shape.** A user of the workspace still reads as `{ id, name, email }` and an id that names nobody as `{ id, name: null }`. An invited person who has not joined reads as `{ id, name, invited: true }` — **with no `email`**, so render `name` and treat `email` as optional. Once they accept, the same id reads as an ordinary user and `invited` is gone.
91
+
92
+ Additive: no existing field changed shape, and the scope is unchanged (`users.write:*` plus the `users.write` capability for the whole invite surface).
78
93
 
79
94
  ### What's new in 0.180.1 (contract 1.141.0 — unchanged)
80
95
 
@@ -2114,9 +2129,17 @@ function PendingInvites() {
2114
2129
  }
2115
2130
  ```
2116
2131
 
2117
- Rows are snake_case: `{ id, email, name, group_ids, status, expires_at, accepted_at,
2118
- revoked_at, created_at }`. Trust the server-computed `status` rather than comparing
2119
- `expires_at` against the device clock.
2132
+ Rows are snake_case: `{ id, app_user_id, email, name, group_ids, status, expires_at,
2133
+ accepted_at, revoked_at, created_at }`. Trust the server-computed `status` rather than
2134
+ comparing `expires_at` against the device clock.
2135
+
2136
+ `query` also takes `q` (a case-insensitive substring match on the invite's name or
2137
+ email) and `app_user_id`. Each row's `app_user_id` is the id the invitee's user account
2138
+ will have once they accept. A `USER` column may hold that id before the person has
2139
+ joined — it then reads back as `{ id, name, invited: true }`, with no `email` — and
2140
+ `useInvites({ app_user_id: value.id, status: 'open', limit: 1 })` returns the invite
2141
+ behind it. `status: 'open'` is every invite that is neither accepted nor revoked
2142
+ (`pending` or `expired`); a re-invite keeps the id, so without it earlier invites match too.
2120
2143
 
2121
2144
  **The gate is `users.write`, not `users.read`.** A pending invite exposes the email of
2122
2145
  someone who is not a member yet, so the backend gates the entire invite surface —
package/dist/contract.cjs CHANGED
@@ -1787,9 +1787,18 @@ const HOOKS = [
1787
1787
  "returns the { data, meta } envelope verbatim — the hook unwraps " +
1788
1788
  "res.data; rows are snake_case (email, status, expires_at, …) and " +
1789
1789
  "status is the server-computed pending/accepted/revoked/expired " +
1790
- "value. query is { status?, limit?, offset? } passed verbatim; the " +
1791
- "endpoint defaults to status 'all', so pass { status: 'pending' } " +
1792
- "for the usual outstanding-invite list. The WHOLE invite surface — " +
1790
+ "value. query is { status?, q?, app_user_id?, limit?, offset? } passed " +
1791
+ "verbatim; the endpoint defaults to status 'all', so pass " +
1792
+ "{ status: 'pending' } for the usual outstanding-invite list; " +
1793
+ "status 'open' is every invite neither accepted nor revoked (pending " +
1794
+ "or expired). q is a " +
1795
+ "case-insensitive substring match on the invite's name or email. " +
1796
+ "Each row's app_user_id is the id the invitee's user account will " +
1797
+ "have once they accept; a USER column may already hold it, and " +
1798
+ "{ app_user_id, status: 'open' } returns the invite behind such an id " +
1799
+ "(a re-invite keeps the id, so without the status earlier invites " +
1800
+ "match too; an id no invite reserves returns no rows). The WHOLE " +
1801
+ "invite surface — " +
1793
1802
  "list included — is gated on users.write:* plus the users.write " +
1794
1803
  "SystemAcl capability, because a pending invite exposes the email of " +
1795
1804
  "someone who is not a member yet; a users.read-only caller cannot see " +
@@ -1799,7 +1808,7 @@ const HOOKS = [
1799
1808
  "refetch on success.",
1800
1809
  returnShape: {
1801
1810
  invites:
1802
- "Array<{ id, email, name, group_ids, status, expires_at, accepted_at, revoked_at, created_at }> // snake_case rows; unwrapped from { data, meta }",
1811
+ "Array<{ id, app_user_id, email, name, group_ids, status, expires_at, accepted_at, revoked_at, created_at }> // snake_case rows; unwrapped from { data, meta }",
1803
1812
  loading: "boolean",
1804
1813
  error: "DirectoryError | null // { code, message, retryable }",
1805
1814
  refetch: "() => Promise<void>",
@@ -4767,7 +4776,13 @@ const CONTRACT = deepFreeze({
4767
4776
  // 1.141.0: additive (sc-9346) — inside a section that paints its own fill,
4768
4777
  // `theme.colors.backdrop` is that fill, and `<Button>` re-inks a variant
4769
4778
  // colour that cannot be read against it by the rule its text follows.
4770
- version: "1.141.0",
4779
+ // 1.142.0: additive (sc-9713) — `useInvites(query)` takes `q`,
4780
+ // `app_user_id` and `status: "open"`, and each invite row carries
4781
+ // `app_user_id`: the id the invitee's user account will have once they
4782
+ // accept.
4783
+ // 1.143.0: additive (sc-9756) — a datastoreTemplate RELATION column may
4784
+ // declare `onDelete` ("CASCADE" | "SET_NULL") for ONE_TO_ONE/ONE_TO_MANY.
4785
+ version: "1.143.0",
4771
4786
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
4772
4787
  hooks: HOOKS,
4773
4788
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -1787,9 +1787,18 @@ const HOOKS = [
1787
1787
  "returns the { data, meta } envelope verbatim — the hook unwraps " +
1788
1788
  "res.data; rows are snake_case (email, status, expires_at, …) and " +
1789
1789
  "status is the server-computed pending/accepted/revoked/expired " +
1790
- "value. query is { status?, limit?, offset? } passed verbatim; the " +
1791
- "endpoint defaults to status 'all', so pass { status: 'pending' } " +
1792
- "for the usual outstanding-invite list. The WHOLE invite surface — " +
1790
+ "value. query is { status?, q?, app_user_id?, limit?, offset? } passed " +
1791
+ "verbatim; the endpoint defaults to status 'all', so pass " +
1792
+ "{ status: 'pending' } for the usual outstanding-invite list; " +
1793
+ "status 'open' is every invite neither accepted nor revoked (pending " +
1794
+ "or expired). q is a " +
1795
+ "case-insensitive substring match on the invite's name or email. " +
1796
+ "Each row's app_user_id is the id the invitee's user account will " +
1797
+ "have once they accept; a USER column may already hold it, and " +
1798
+ "{ app_user_id, status: 'open' } returns the invite behind such an id " +
1799
+ "(a re-invite keeps the id, so without the status earlier invites " +
1800
+ "match too; an id no invite reserves returns no rows). The WHOLE " +
1801
+ "invite surface — " +
1793
1802
  "list included — is gated on users.write:* plus the users.write " +
1794
1803
  "SystemAcl capability, because a pending invite exposes the email of " +
1795
1804
  "someone who is not a member yet; a users.read-only caller cannot see " +
@@ -1799,7 +1808,7 @@ const HOOKS = [
1799
1808
  "refetch on success.",
1800
1809
  returnShape: {
1801
1810
  invites:
1802
- "Array<{ id, email, name, group_ids, status, expires_at, accepted_at, revoked_at, created_at }> // snake_case rows; unwrapped from { data, meta }",
1811
+ "Array<{ id, app_user_id, email, name, group_ids, status, expires_at, accepted_at, revoked_at, created_at }> // snake_case rows; unwrapped from { data, meta }",
1803
1812
  loading: "boolean",
1804
1813
  error: "DirectoryError | null // { code, message, retryable }",
1805
1814
  refetch: "() => Promise<void>",
@@ -4763,7 +4772,13 @@ const CONTRACT = deepFreeze({
4763
4772
  // 1.141.0: additive (sc-9346) — inside a section that paints its own fill,
4764
4773
  // `theme.colors.backdrop` is that fill, and `<Button>` re-inks a variant
4765
4774
  // colour that cannot be read against it by the rule its text follows.
4766
- version: "1.141.0",
4775
+ // 1.142.0: additive (sc-9713) — `useInvites(query)` takes `q`,
4776
+ // `app_user_id` and `status: "open"`, and each invite row carries
4777
+ // `app_user_id`: the id the invitee's user account will have once they
4778
+ // accept.
4779
+ // 1.143.0: additive (sc-9756) — a datastoreTemplate RELATION column may
4780
+ // declare `onDelete` ("CASCADE" | "SET_NULL") for ONE_TO_ONE/ONE_TO_MANY.
4781
+ version: "1.143.0",
4767
4782
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
4768
4783
  hooks: HOOKS,
4769
4784
  primitives: PRIMITIVES,
package/dist/index.d.ts CHANGED
@@ -187,6 +187,12 @@ export interface WidgetDatastoreTemplateColumn {
187
187
  relationType?: "ONE_TO_ONE" | "ONE_TO_MANY" | "MANY_TO_MANY";
188
188
  /** REQ-ACL-RELINHERIT: opt this RELATION column into row-level ACL inheritance. */
189
189
  inheritAcl?: boolean;
190
+ /**
191
+ * ONE_TO_ONE / ONE_TO_MANY only: what deleting the referenced row does to
192
+ * this table's rows. `CASCADE` deletes them too; `SET_NULL` (the default)
193
+ * empties the reference. A MANY_TO_MANY link is always removed.
194
+ */
195
+ onDelete?: "CASCADE" | "SET_NULL";
190
196
  /**
191
197
  * REQ-DDL-ENCRYPT: store this column's values encrypted at rest (AES-256-GCM
192
198
  * under a per-workspace subkey). The widget still reads and writes plaintext
@@ -2185,6 +2191,12 @@ export type AppUserInviteStatus =
2185
2191
 
2186
2192
  export interface AppUserInviteRow {
2187
2193
  id: string;
2194
+ /**
2195
+ * The id the invitee's user account will have once they accept. On every
2196
+ * `useInvites()` row and the `resend()` result; `useUsers().invite()` does
2197
+ * not return it.
2198
+ */
2199
+ app_user_id?: string;
2188
2200
  email: string;
2189
2201
  /** Server-computed lifecycle value — prefer it over re-deriving expiry. */
2190
2202
  status: AppUserInviteStatus;
@@ -2258,8 +2270,20 @@ export function useGroups(query?: GroupsQuery): GroupsApi;
2258
2270
  // sc-5097 — pending AppUser invite administration.
2259
2271
 
2260
2272
  export interface InvitesQuery {
2261
- /** Defaults to "all" server-side; pass "pending" for outstanding invites. */
2262
- status?: AppUserInviteStatus | "all";
2273
+ /**
2274
+ * Defaults to "all" server-side; pass "pending" for outstanding invites.
2275
+ * "open" is "pending" or "expired": neither accepted nor revoked.
2276
+ */
2277
+ status?: AppUserInviteStatus | "open" | "all";
2278
+ /** Case-insensitive substring match on the invite's name or email. */
2279
+ q?: string;
2280
+ /**
2281
+ * Only the invites reserving this user id (a row's `app_user_id`) — resolves
2282
+ * the person behind an id a USER column holds before they have joined. A
2283
+ * re-invite keeps the id, so add `status: "open"` for the one that still
2284
+ * reserves it. An id no invite reserves returns no rows.
2285
+ */
2286
+ app_user_id?: string;
2263
2287
  limit?: number;
2264
2288
  offset?: number;
2265
2289
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.180.1",
3
+ "version": "0.182.0",
4
4
  "description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
5
5
  "homepage": "https://github.com/Colix-AB/AppStudio",
6
6
  "type": "module",