@colixsystems/widget-sdk 0.136.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 CHANGED
@@ -70,7 +70,32 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
70
70
 
71
71
  ## Status
72
72
 
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**.
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.
74
99
 
75
100
  ### What's new in 0.136.0 (contract 1.105.0)
76
101
 
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. 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.",
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",
@@ -3926,7 +3926,7 @@ const CONTRACT = deepFreeze({
3926
3926
  // template declaring neither behaves exactly as before, and a role nobody
3927
3927
  // binds writes no grant rather than failing the install. Minor bump on the
3928
3928
  // pre-1.0 channel.
3929
- version: "1.105.0",
3929
+ version: "1.106.0",
3930
3930
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
3931
3931
  hooks: HOOKS,
3932
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. 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.",
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",
@@ -3926,7 +3926,7 @@ const CONTRACT = deepFreeze({
3926
3926
  // template declaring neither behaves exactly as before, and a role nobody
3927
3927
  // binds writes no grant rather than failing the install. Minor bump on the
3928
3928
  // pre-1.0 channel.
3929
- version: "1.105.0",
3929
+ version: "1.106.0",
3930
3930
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
3931
3931
  hooks: HOOKS,
3932
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.136.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",