@colixsystems/widget-sdk 0.135.0 → 0.137.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 +59 -1
- package/dist/contract.cjs +15 -2
- package/dist/contract.js +15 -2
- package/dist/index.d.ts +53 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,7 +70,65 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
70
70
|
|
|
71
71
|
## Status
|
|
72
72
|
|
|
73
|
-
`v0.
|
|
73
|
+
`v0.137.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.137.0 (contract 1.106.0)
|
|
76
|
+
|
|
77
|
+
**A `datastoreTemplate` column marked `encrypted` is now actually encrypted in the workspace that installs you (sc-7557).** The field was documented nowhere and honoured nowhere: install built its column rows one way, the republish/upgrade migration built them another way, and neither carried the flag — so a column you declared confidential was created as an ordinary plaintext column and nothing told you or your installer. Both paths now share one projection, so the flag lands on install and on upgrade alike.
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
datastoreTemplate: {
|
|
81
|
+
tables: [
|
|
82
|
+
{
|
|
83
|
+
suffix: "Patients",
|
|
84
|
+
columns: [
|
|
85
|
+
{ name: "Name", dataType: "STRING", required: true },
|
|
86
|
+
{ name: "Ssn", dataType: "STRING", required: true, encrypted: true },
|
|
87
|
+
],
|
|
88
|
+
rows: [{ Name: "Ada Lovelace", Ssn: "600101-1234" }],
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- **Your widget code does not change.** It reads and writes plaintext through `useDatastoreQuery` / `useDatastoreMutation` exactly as it does for any other column — the platform encrypts on write (AES-256-GCM under a per-workspace subkey) and decrypts for end users.
|
|
95
|
+
- **Studio users see `🔒`, not the value.** That is the point of the flag: the people authoring the app cannot read what their end users store. The column also cannot be searched, filtered or sorted on — ciphertext is opaque.
|
|
96
|
+
- **Sample `rows` are encrypted too.** They used to be written straight into the value table; a plaintext row under an encrypted column would have been read back as a corrupt envelope.
|
|
97
|
+
- **Not valid on `RELATION`.** Its value is a foreign key the backend has to resolve, so it can never be opaque — declaring it is now a publish error, as is a non-boolean `encrypted`.
|
|
98
|
+
- **An upgrade adds a new encrypted column, but never flips a live one.** Turning encryption on over existing plaintext (or off over existing ciphertext) would strand what is already stored, so a changed flag on a column that already exists is reported back to you rather than applied.
|
|
99
|
+
|
|
100
|
+
### What's new in 0.136.0 (contract 1.105.0)
|
|
101
|
+
|
|
102
|
+
**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.
|
|
103
|
+
|
|
104
|
+
Templates now name their audiences symbolically and let the installer bind them:
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
datastoreTemplate: {
|
|
108
|
+
roles: [
|
|
109
|
+
{ key: "admin", label: "Administrators", description: "Manages orders end to end" },
|
|
110
|
+
],
|
|
111
|
+
tables: [
|
|
112
|
+
{
|
|
113
|
+
suffix: "Orders",
|
|
114
|
+
columns: [...],
|
|
115
|
+
// the id-less audiences, unchanged
|
|
116
|
+
publicGrant: { canRead: true, canWrite: false, canDelete: false },
|
|
117
|
+
// the audience that needs a real group
|
|
118
|
+
roleGrants: [{ role: "admin", canRead: true, canWrite: true, canDelete: true }],
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`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.
|
|
125
|
+
|
|
126
|
+
- **`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.
|
|
127
|
+
- **`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.
|
|
128
|
+
- **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.
|
|
129
|
+
- **Purely additive.** A template declaring neither `roles` nor `roleGrants` behaves exactly as it does today.
|
|
130
|
+
|
|
131
|
+
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
132
|
|
|
75
133
|
### What's new in 0.135.0 (contract 1.104.0)
|
|
76
134
|
|
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. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected.",
|
|
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
|
-
|
|
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.106.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. sc-7557: a column may declare `encrypted: true` — its values are stored AES-256-GCM encrypted at rest under a per-workspace subkey. The widget still reads and writes PLAINTEXT (the platform encrypts on write, decrypts for end users); studio users see `🔒` instead, and the column cannot be searched, filtered or sorted on. Sample `rows` you seed for it are encrypted the same way. Not valid on `RELATION` — its value is a foreign key the backend must resolve — and publishing one is rejected.",
|
|
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
|
-
|
|
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.106.0",
|
|
3917
3930
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
3918
3931
|
hooks: HOOKS,
|
|
3919
3932
|
primitives: PRIMITIVES,
|
package/dist/index.d.ts
CHANGED
|
@@ -186,6 +186,15 @@ export interface WidgetDatastoreTemplateColumn {
|
|
|
186
186
|
relationType?: "ONE_TO_ONE" | "ONE_TO_MANY" | "MANY_TO_MANY";
|
|
187
187
|
/** REQ-ACL-RELINHERIT: opt this RELATION column into row-level ACL inheritance. */
|
|
188
188
|
inheritAcl?: boolean;
|
|
189
|
+
/**
|
|
190
|
+
* REQ-DDL-ENCRYPT: store this column's values encrypted at rest (AES-256-GCM
|
|
191
|
+
* under a per-workspace subkey). The widget still reads and writes plaintext
|
|
192
|
+
* — the platform encrypts on write and decrypts for end users — but studio
|
|
193
|
+
* users see `🔒` instead of the value, and the column cannot be searched,
|
|
194
|
+
* filtered or sorted on. Not valid on `RELATION` (its value is a foreign key
|
|
195
|
+
* the backend must resolve); publishing one is rejected.
|
|
196
|
+
*/
|
|
197
|
+
encrypted?: boolean;
|
|
189
198
|
}
|
|
190
199
|
|
|
191
200
|
export interface WidgetDatastoreTemplateTable {
|
|
@@ -201,6 +210,14 @@ export interface WidgetDatastoreTemplateTable {
|
|
|
201
210
|
* studio-owner only regardless.
|
|
202
211
|
*/
|
|
203
212
|
publicGrant?: { canRead?: boolean; canWrite?: boolean; canDelete?: boolean };
|
|
213
|
+
/**
|
|
214
|
+
* sc-7530: group-scoped grants, named symbolically. Each entry's `role` must
|
|
215
|
+
* be a `key` declared on the template's `roles`. Unlike `publicGrant` — which
|
|
216
|
+
* can only reach the two synthetic principals (`everyone` / `authenticated`)
|
|
217
|
+
* — these express "this audience gets these verbs" and are bound to a real
|
|
218
|
+
* group by the workspace that installs the widget.
|
|
219
|
+
*/
|
|
220
|
+
roleGrants?: WidgetDatastoreTemplateRoleGrant[];
|
|
204
221
|
columns: WidgetDatastoreTemplateColumn[];
|
|
205
222
|
/**
|
|
206
223
|
* Optional sample rows seeded into the table at install time so the widget
|
|
@@ -213,8 +230,44 @@ export interface WidgetDatastoreTemplateTable {
|
|
|
213
230
|
>;
|
|
214
231
|
}
|
|
215
232
|
|
|
233
|
+
/**
|
|
234
|
+
* sc-7530: a role the template's `roleGrants` name. Declared once per template
|
|
235
|
+
* and referenced by every table, so several widgets over several tables ask the
|
|
236
|
+
* installing workspace ONE question per audience rather than one per grant.
|
|
237
|
+
*
|
|
238
|
+
* Symbolic on purpose. A group id is tenant-local, so it could only ever be
|
|
239
|
+
* wrong in the workspace that installs the widget — the installer binds each
|
|
240
|
+
* role to one of their OWN groups (or has one created) at install time.
|
|
241
|
+
*/
|
|
242
|
+
export interface WidgetDatastoreTemplateRole {
|
|
243
|
+
/** Stable within the template; matches /^[a-z][a-z0-9-]*$/. */
|
|
244
|
+
key: string;
|
|
245
|
+
/** What the installing workspace sees in the binding step. */
|
|
246
|
+
label: string;
|
|
247
|
+
/** Why the role exists — shown under the label. */
|
|
248
|
+
description?: string;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** sc-7530: the verbs one role holds over one table. */
|
|
252
|
+
export interface WidgetDatastoreTemplateRoleGrant {
|
|
253
|
+
/** A `key` from the template's `roles`. */
|
|
254
|
+
role: string;
|
|
255
|
+
/** Read the table and the rows in it. */
|
|
256
|
+
canRead?: boolean;
|
|
257
|
+
/** Create records, and edit existing ones. */
|
|
258
|
+
canWrite?: boolean;
|
|
259
|
+
/** Delete records. Row-level only — table scope has no delete verb. */
|
|
260
|
+
canDelete?: boolean;
|
|
261
|
+
}
|
|
262
|
+
|
|
216
263
|
export interface WidgetDatastoreTemplate {
|
|
217
264
|
tables: WidgetDatastoreTemplateTable[];
|
|
265
|
+
/**
|
|
266
|
+
* sc-7530: the audiences this template's tables grant access to. Bound to
|
|
267
|
+
* real `AppUserGroup`s by the installing workspace; an unbound role simply
|
|
268
|
+
* writes no grant.
|
|
269
|
+
*/
|
|
270
|
+
roles?: WidgetDatastoreTemplateRole[];
|
|
218
271
|
}
|
|
219
272
|
|
|
220
273
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.137.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",
|