@colixsystems/widget-sdk 0.135.0 → 0.136.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
@@ -70,7 +70,40 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
70
70
 
71
71
  ## Status
72
72
 
73
- `v0.135.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**.
73
+ `v0.136.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**.
74
+
75
+ ### What's new in 0.136.0 (contract 1.105.0)
76
+
77
+ **A `datastoreTemplate` can now say WHICH AUDIENCE gets which access — not just the two anonymous ones (sc-7530).** `publicGrant` could only ever reach `everyone` and `authenticated`, because those are the two principals that need no id. Anything naming a real user group was unexpressible: a group id belongs to the workspace that installs your widget, so it could never travel in your manifest. The practical cost was that a permission model you had already set up correctly — say an admin group with full CRUD while ordinary signed-in users only read — shipped to your installers as nothing at all, and every one of them rebuilt it by hand across both the table grant and the record permissions.
78
+
79
+ Templates now name their audiences symbolically and let the installer bind them:
80
+
81
+ ```js
82
+ datastoreTemplate: {
83
+ roles: [
84
+ { key: "admin", label: "Administrators", description: "Manages orders end to end" },
85
+ ],
86
+ tables: [
87
+ {
88
+ suffix: "Orders",
89
+ columns: [...],
90
+ // the id-less audiences, unchanged
91
+ publicGrant: { canRead: true, canWrite: false, canDelete: false },
92
+ // the audience that needs a real group
93
+ roleGrants: [{ role: "admin", canRead: true, canWrite: true, canDelete: true }],
94
+ },
95
+ ],
96
+ }
97
+ ```
98
+
99
+ `roles` is declared ONCE per template and referenced by every table, so three widgets over two tables ask the installing workspace **one** question per audience rather than one per grant. At install they pick one of their own groups for each role, or have one created for them under the role's label.
100
+
101
+ - **`key`** is lowercase-kebab (`/^[a-z][a-z0-9-]*$/`), at most 8 roles per template. A `roleGrants` entry naming a role you did not declare is a publish error, as is granting the same role twice on one table or granting it no verb at all.
102
+ - **`canDelete` is row-level only.** A table-scope grant answers "may I create a record"; only a record permission answers a question about a row. Both halves are written for you from the one declaration.
103
+ - **An unbound role is an ordinary outcome, never a failed install.** If the installer skips a role, no grant is written and they are told which role is still unbound — they can bind it later from the table's permissions.
104
+ - **Purely additive.** A template declaring neither `roles` nor `roleGrants` behaves exactly as it does today.
105
+
106
+ Also tightened in this release: the `publicGrant` flags are now type-checked. They never were, so a stringy `canRead: "false"` read as truthy and silently opened the table to anonymous reads. If you have been passing anything but a real boolean there, publishing will now tell you.
74
107
 
75
108
  ### What's new in 0.135.0 (contract 1.104.0)
76
109
 
package/dist/contract.cjs CHANGED
@@ -2153,7 +2153,7 @@ const MANIFEST_SCHEMA = {
2153
2153
  type: "object",
2154
2154
  required: false,
2155
2155
  description:
2156
- "Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 8 tables, 24 columns per table. RELATION columns address siblings by `targetSuffix` (must be declared earlier in the array). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for.",
2156
+ "Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 8 tables, 24 columns per table. RELATION columns address siblings by `targetSuffix` (must be declared earlier in the array). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 8 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install.",
2157
2157
  },
2158
2158
  translations: {
2159
2159
  type: "object",
@@ -3913,7 +3913,20 @@ const CONTRACT = deepFreeze({
3913
3913
  // the web half is a widget.web.jsx typed-code or QR path. Pinned in the
3914
3914
  // export and gated behind the `nfc` export capability, because its iOS
3915
3915
  // entitlement obliges every App ID that carries it.
3916
- version: "1.104.0",
3916
+ // 1.105.0: additive (sc-7530) — a `datastoreTemplate` can declare the
3917
+ // AUDIENCES its tables grant access to, not just the two id-less ones.
3918
+ // `publicGrant` could only ever reach `everyone` and `authenticated`, so a
3919
+ // widget whose author had set up "an admin group does everything, signed-in
3920
+ // users only read" shipped that model to nobody: a group id is tenant-local
3921
+ // and cannot travel in a manifest. Templates now declare `roles:
3922
+ // [{ key, label, description? }]` once (lowercase-kebab key, max 8) and
3923
+ // per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`; the
3924
+ // installing workspace binds each role to one of ITS OWN user groups — or
3925
+ // has one created — during the install consent step. Purely additive: a
3926
+ // template declaring neither behaves exactly as before, and a role nobody
3927
+ // binds writes no grant rather than failing the install. Minor bump on the
3928
+ // pre-1.0 channel.
3929
+ version: "1.105.0",
3917
3930
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
3918
3931
  hooks: HOOKS,
3919
3932
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -2153,7 +2153,7 @@ const MANIFEST_SCHEMA = {
2153
2153
  type: "object",
2154
2154
  required: false,
2155
2155
  description:
2156
- "Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 8 tables, 24 columns per table. RELATION columns address siblings by `targetSuffix` (must be declared earlier in the array). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for.",
2156
+ "Optional. Tables the widget needs, seeded into the workspace at install time. Authors wire them into the widget's `tableRef` properties via the Properties Panel — the SDK does not auto-bind. Limits: 8 tables, 24 columns per table. RELATION columns address siblings by `targetSuffix` (must be declared earlier in the array). Tables persist across uninstalls. A `tableRef` property may also carry `sharedTable: true`, declaring that the table is the APP'S OWN — the author picks an existing table and the widget seeds nothing for it. That is the correct shape for a widget that reads data it does not own (a chart, a metric, a manager view over another widget's table). `sharedTable` exempts the property from needing a seeded table at publish (`manifest.requiredTableRefsHaveTemplate`) WITHOUT making it optional: the author must still bind one, and the host still refuses to place the widget unbound. Use it instead of dropping `required` — a widget that silently renders an empty tile is the defect it looks like a fix for. sc-7530: a table may also declare `roleGrants` — group-scoped access named SYMBOLICALLY, because a group id belongs to the workspace that installs the widget and could never travel in the manifest. Declare the audiences once at template level as `roles: [{ key, label, description? }]` (key is lowercase kebab, max 8 roles), then per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`. The installing workspace binds each role to one of its own user groups (or has one created) during the install consent step, so a permission model where an admin group does everything and signed-in users only read survives the trip between workspaces. Use `publicGrant` for the two id-less audiences (everyone / any signed-in user) and `roleGrants` for anything that names a group. A role nobody binds simply writes no grant — it never fails the install.",
2157
2157
  },
2158
2158
  translations: {
2159
2159
  type: "object",
@@ -3913,7 +3913,20 @@ const CONTRACT = deepFreeze({
3913
3913
  // the web half is a widget.web.jsx typed-code or QR path. Pinned in the
3914
3914
  // export and gated behind the `nfc` export capability, because its iOS
3915
3915
  // entitlement obliges every App ID that carries it.
3916
- version: "1.104.0",
3916
+ // 1.105.0: additive (sc-7530) — a `datastoreTemplate` can declare the
3917
+ // AUDIENCES its tables grant access to, not just the two id-less ones.
3918
+ // `publicGrant` could only ever reach `everyone` and `authenticated`, so a
3919
+ // widget whose author had set up "an admin group does everything, signed-in
3920
+ // users only read" shipped that model to nobody: a group id is tenant-local
3921
+ // and cannot travel in a manifest. Templates now declare `roles:
3922
+ // [{ key, label, description? }]` once (lowercase-kebab key, max 8) and
3923
+ // per table `roleGrants: [{ role, canRead?, canWrite?, canDelete? }]`; the
3924
+ // installing workspace binds each role to one of ITS OWN user groups — or
3925
+ // has one created — during the install consent step. Purely additive: a
3926
+ // template declaring neither behaves exactly as before, and a role nobody
3927
+ // binds writes no grant rather than failing the install. Minor bump on the
3928
+ // pre-1.0 channel.
3929
+ version: "1.105.0",
3917
3930
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
3918
3931
  hooks: HOOKS,
3919
3932
  primitives: PRIMITIVES,
package/dist/index.d.ts CHANGED
@@ -201,6 +201,14 @@ export interface WidgetDatastoreTemplateTable {
201
201
  * studio-owner only regardless.
202
202
  */
203
203
  publicGrant?: { canRead?: boolean; canWrite?: boolean; canDelete?: boolean };
204
+ /**
205
+ * sc-7530: group-scoped grants, named symbolically. Each entry's `role` must
206
+ * be a `key` declared on the template's `roles`. Unlike `publicGrant` — which
207
+ * can only reach the two synthetic principals (`everyone` / `authenticated`)
208
+ * — these express "this audience gets these verbs" and are bound to a real
209
+ * group by the workspace that installs the widget.
210
+ */
211
+ roleGrants?: WidgetDatastoreTemplateRoleGrant[];
204
212
  columns: WidgetDatastoreTemplateColumn[];
205
213
  /**
206
214
  * Optional sample rows seeded into the table at install time so the widget
@@ -213,8 +221,44 @@ export interface WidgetDatastoreTemplateTable {
213
221
  >;
214
222
  }
215
223
 
224
+ /**
225
+ * sc-7530: a role the template's `roleGrants` name. Declared once per template
226
+ * and referenced by every table, so several widgets over several tables ask the
227
+ * installing workspace ONE question per audience rather than one per grant.
228
+ *
229
+ * Symbolic on purpose. A group id is tenant-local, so it could only ever be
230
+ * wrong in the workspace that installs the widget — the installer binds each
231
+ * role to one of their OWN groups (or has one created) at install time.
232
+ */
233
+ export interface WidgetDatastoreTemplateRole {
234
+ /** Stable within the template; matches /^[a-z][a-z0-9-]*$/. */
235
+ key: string;
236
+ /** What the installing workspace sees in the binding step. */
237
+ label: string;
238
+ /** Why the role exists — shown under the label. */
239
+ description?: string;
240
+ }
241
+
242
+ /** sc-7530: the verbs one role holds over one table. */
243
+ export interface WidgetDatastoreTemplateRoleGrant {
244
+ /** A `key` from the template's `roles`. */
245
+ role: string;
246
+ /** Read the table and the rows in it. */
247
+ canRead?: boolean;
248
+ /** Create records, and edit existing ones. */
249
+ canWrite?: boolean;
250
+ /** Delete records. Row-level only — table scope has no delete verb. */
251
+ canDelete?: boolean;
252
+ }
253
+
216
254
  export interface WidgetDatastoreTemplate {
217
255
  tables: WidgetDatastoreTemplateTable[];
256
+ /**
257
+ * sc-7530: the audiences this template's tables grant access to. Bound to
258
+ * real `AppUserGroup`s by the installing workspace; an unbound role simply
259
+ * writes no grant.
260
+ */
261
+ roles?: WidgetDatastoreTemplateRole[];
218
262
  }
219
263
 
220
264
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.135.0",
3
+ "version": "0.136.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",