@colixsystems/widget-sdk 0.263.0 → 0.265.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 +10 -2
- package/dist/contract.cjs +20 -7
- package/dist/contract.js +20 -7
- package/dist/hooks.js +8 -1
- package/dist/index.d.ts +9 -5
- package/package.json +1 -1
- package/src/contract.js +20 -7
package/README.md
CHANGED
|
@@ -86,7 +86,15 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
86
86
|
|
|
87
87
|
## Status
|
|
88
88
|
|
|
89
|
-
`v0.
|
|
89
|
+
`v0.265.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**.
|
|
90
|
+
|
|
91
|
+
### What's new in 0.265.0 (contract 1.215.0)
|
|
92
|
+
|
|
93
|
+
- **`usePdfExport` appends stored PDFs.** `openPdf(html, { appendFileIds })` and `exportToPdf(html, { appendFileIds })` take up to 20 Filestore file ids; every page of each PDF follows the rendered HTML, in order, on the web Player and in the native export alike (the merge runs server-side). An id the user cannot download, or a file that is not a readable PDF (password-protected, malformed), rejects the call with `err.details.file_id` naming it: 404 `FILE_NOT_FOUND`, 403 `FILE_DOWNLOAD_NOT_PERMITTED`, 422 `PDF_NOT_MERGEABLE`.
|
|
94
|
+
|
|
95
|
+
### What's new in 0.264.0 (contract 1.214.0)
|
|
96
|
+
|
|
97
|
+
**A `datastoreTemplate` column can be a `UUID`.** It holds a UUID, checked on every write and stored lowercase, so an id typed in capitals still matches. `defaultValue: "@uuid"` gives every new row its own random id, and `defaultLocked: true` with it makes that id server-generated and unforgeable. Sample `rows` may seed one. `DatastoreSchemaColumn.data_type` now also lists `UUID` (and `DATETIME`, which was missing). Additive.
|
|
90
98
|
|
|
91
99
|
### What's new in 0.263.0 (contract 1.213.0)
|
|
92
100
|
|
|
@@ -1019,7 +1027,7 @@ See [Bundled images and sounds](#bundled-images-and-sounds). Additive: a widget
|
|
|
1019
1027
|
|
|
1020
1028
|
**Files can be attached to a datastore record and stay private to it.** `useFilestoreUpload({ spaceType: "record", recordId })` attaches the uploaded file to that record, and `useFilestoreFiles({ spaceType: "record", recordId, type })` lists its attachments newest first (`type: "media"` for images and videos, `"document"` for the rest). Exactly the people who may read the record may read its attachments, through the same permission check as the record itself. Uploading needs write on the record, only the uploader may delete an attachment, and removing the record removes its attachments. A record attachment's URL is issued to one viewer and lives about ten minutes, so re-resolve it (`useFilestoreFile(id).refetch()`) when an image fails to load. The new `useFilestoreDelete()` returns `{ remove, removing, error }`: `remove(fileId)` deletes a record attachment permanently (only its uploader may) and moves any other file to the trash. `useFilestoreCopy().copy(fileId, { spaceType: "record", recordId })` attaches a copy of a file to a record, with the same write check as uploading there. `useCamera().pick({ multiple: true })` lets the user choose up to 10 photos from the device library at once and resolves an array of assets; `pick()` alone is unchanged. `<Avatar seed={user.id} />` draws the initials on that person's own colour, the same one the Studio's member lists use, and `avatarColorFor(seed)` returns it.
|
|
1021
1029
|
|
|
1022
|
-
**A `datastoreTemplate` can make inherited access read-only and stamp author columns on the server.** An `inheritAcl` RELATION column may add `inheritAclReadOnly: true`. Rows then inherit only READ access from the row they point at: members of a conversation read every message in it and may post into it, but change or delete only the messages they created (pair it with `grantCreatorPermissions`). This also holds down a chain, such as a reaction on a message in a conversation. A column may declare `defaultValue`, which is written when a create omits the column. Use `"@currentUser"` on a USER column, `"@now"` on a DATE or DATETIME column, or a literal of the column's own type. FILE, RELATION and USER_GROUP columns take no default, because ids belong to the installing workspace. `defaultLocked: true` on a `"@currentUser"` or `"@now"` default makes the server always stamp it. Any value a client sends for the column is ignored on create and on update, so a `Sender` or `CreatedBy` column is always the real author. `immutable: true` makes a column write-once: a create sets it from the value sent or the default, and any value sent for it on update is ignored, so a message's `Conversation` cannot be moved after it is posted. It is not valid on a MANY_TO_MANY relation, whose links live outside the row. Additive.
|
|
1030
|
+
**A `datastoreTemplate` can make inherited access read-only and stamp author columns on the server.** An `inheritAcl` RELATION column may add `inheritAclReadOnly: true`. Rows then inherit only READ access from the row they point at: members of a conversation read every message in it and may post into it, but change or delete only the messages they created (pair it with `grantCreatorPermissions`). This also holds down a chain, such as a reaction on a message in a conversation. A column may declare `defaultValue`, which is written when a create omits the column. Use `"@currentUser"` on a USER column, `"@now"` on a DATE or DATETIME column, `"@uuid"` on a UUID column, or a literal of the column's own type. FILE, RELATION and USER_GROUP columns take no default, because ids belong to the installing workspace. `defaultLocked: true` on a `"@currentUser"` or `"@now"` default makes the server always stamp it. Any value a client sends for the column is ignored on create and on update, so a `Sender` or `CreatedBy` column is always the real author. `immutable: true` makes a column write-once: a create sets it from the value sent or the default, and any value sent for it on update is ignored, so a message's `Conversation` cannot be moved after it is posted. It is not valid on a MANY_TO_MANY relation, whose links live outside the row. Additive.
|
|
1023
1031
|
|
|
1024
1032
|
**A user can mute one record's notifications.** `useRecordMute(tableId, recordId)` returns `{ muted, mutedUntil, loading, error, mute, unmute, refetch }`. `mute(until)` takes an ISO time or a `Date`, or `null` (or nothing) to mute until `unmute()`. While a mute is active, a server action's `notifications.notifyRecordSubjects` on that record sends the user nothing: no inbox row, no push, no email. `mutedUntil` is the ISO time the mute ends, or `null` when it lasts until unmuted, and `muted` turns false by itself when that time passes. The user must be able to read the record, so a record they cannot read fails with `NotificationError { code: "RECORD_NOT_FOUND" }`, just like one that does not exist. A falsy id returns a stable not-muted result with no request. On a host without `ctx.notifications.mutes` (the Builder canvas preview) the hook reads not muted, and `mute()` / `unmute()` reject with `NOTIFICATIONS_UNAVAILABLE`. No scope is needed, because it only changes the user's own setting. To show mutes in a list, call `useRecordMutes(tableId)` once: `isMuted(recordId)` answers for every row from one request, and a mute made through `useRecordMute` on the same table shows at once. `NotificationError.code` now carries the server's code when it sends one (`INVALID_TITLE`, `RECORD_NOT_FOUND`, …), and a 401 reads `AUTH_REQUIRED`. Additive.
|
|
1025
1033
|
|
package/dist/contract.cjs
CHANGED
|
@@ -1539,7 +1539,7 @@ const HOOKS = [
|
|
|
1539
1539
|
optionalArgs: ["spaceType", "folderId"],
|
|
1540
1540
|
description:
|
|
1541
1541
|
"Render an HTML string to a PDF server-side, then either OPEN it or SAVE " +
|
|
1542
|
-
"it. `openPdf(html, { fileName? })` is THE way to give the user a PDF " +
|
|
1542
|
+
"it. `openPdf(html, { fileName?, appendFileIds? })` is THE way to give the user a PDF " +
|
|
1543
1543
|
"to view, print, download or share: it renders the HTML and opens the " +
|
|
1544
1544
|
"result — the browser's PDF viewer in a new tab on the web Player, the " +
|
|
1545
1545
|
"OS print / share sheet in the native export — and SAVES NOTHING, so " +
|
|
@@ -1555,19 +1555,27 @@ const HOOKS = [
|
|
|
1555
1555
|
"the end-user's Filestore space. The widget passes the SPACE " +
|
|
1556
1556
|
"(`{ spaceType, folderId? }`); the hook resolves owner_id from the host " +
|
|
1557
1557
|
"context and POSTs JSON through ctx.filestore.files.exportPdf. " +
|
|
1558
|
-
"`exportToPdf(html, { fileName?, folderId? })` resolves to the created " +
|
|
1558
|
+
"`exportToPdf(html, { fileName?, folderId?, appendFileIds? })` resolves to the created " +
|
|
1559
1559
|
"file row (mime_type `application/pdf`) or throws the wire error; " +
|
|
1560
1560
|
"`err.status` 403/404 means the destination denied the add (403 " +
|
|
1561
1561
|
"when the caller can see the position, 404 when it cannot), a 413 " +
|
|
1562
1562
|
"that the rendered " +
|
|
1563
1563
|
"PDF exceeded the size cap. The HTML is rendered by the host's PDF " +
|
|
1564
1564
|
"service (headless Chromium) — the SAME server-side pipeline on the web " +
|
|
1565
|
-
"Player and the native export, so no browser-only PDF library is used."
|
|
1565
|
+
"Player and the native export, so no browser-only PDF library is used. " +
|
|
1566
|
+
"To put a record's attached PDFs INSIDE the document, pass their Filestore " +
|
|
1567
|
+
"file ids as `appendFileIds` (at most 20) to either call: every page of " +
|
|
1568
|
+
"each file follows the rendered HTML, in the order given. Do not try to " +
|
|
1569
|
+
"embed a PDF through an <img>, <iframe> or link. Each id must be a PDF the " +
|
|
1570
|
+
"user may download, or the call rejects naming it in `err.details.file_id`: " +
|
|
1571
|
+
"404 FILE_NOT_FOUND, 403 FILE_DOWNLOAD_NOT_PERMITTED, or 422 " +
|
|
1572
|
+
"PDF_NOT_MERGEABLE (not a PDF, password-protected, or unreadable). On a " +
|
|
1573
|
+
"422, drop that id and call again so the rest of the report still opens.",
|
|
1566
1574
|
returnShape: {
|
|
1567
|
-
openPdf: "(html, { fileName? }) => Promise<void>",
|
|
1575
|
+
openPdf: "(html, { fileName?, appendFileIds? }) => Promise<void>",
|
|
1568
1576
|
opening: "boolean",
|
|
1569
1577
|
canOpenPdf: "boolean",
|
|
1570
|
-
exportToPdf: "(html, { fileName?, folderId? }) => Promise<FilestoreFile>",
|
|
1578
|
+
exportToPdf: "(html, { fileName?, folderId?, appendFileIds? }) => Promise<FilestoreFile>",
|
|
1571
1579
|
exporting: "boolean",
|
|
1572
1580
|
error: "Error | null",
|
|
1573
1581
|
lastExported: "FilestoreFile | null",
|
|
@@ -3959,7 +3967,7 @@ const MANIFEST_SCHEMA = {
|
|
|
3959
3967
|
type: "object",
|
|
3960
3968
|
required: false,
|
|
3961
3969
|
description:
|
|
3962
|
-
"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: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). 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 32 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. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
3970
|
+
"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: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). 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 32 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. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, `@uuid` on UUID (a new random id per row), or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` / `@uuid` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
3963
3971
|
},
|
|
3964
3972
|
translations: {
|
|
3965
3973
|
type: "object",
|
|
@@ -6432,7 +6440,12 @@ const CONTRACT = deepFreeze({
|
|
|
6432
6440
|
// scope. A widget runs one of the workspace's Actions and reads its result.
|
|
6433
6441
|
// 1.213.0: behavioural (sc-13933) — `<Tabs>` wraps tabs that do not fit onto
|
|
6434
6442
|
// another line instead of scrolling them out of sight.
|
|
6435
|
-
|
|
6443
|
+
// 1.214.0: additive (sc-13746) — a datastoreTemplate column may be a
|
|
6444
|
+
// `UUID` (validated, stored lowercase); `"@uuid"` defaults it to a new
|
|
6445
|
+
// random id per row and may be `defaultLocked`.
|
|
6446
|
+
// 1.215.0: additive (sc-13938) — `usePdfExport` `openPdf` / `exportToPdf`
|
|
6447
|
+
// take `appendFileIds`: stored PDFs whose pages follow the rendered HTML.
|
|
6448
|
+
version: "1.215.0",
|
|
6436
6449
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6437
6450
|
hooks: HOOKS,
|
|
6438
6451
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -1536,7 +1536,7 @@ const HOOKS = [
|
|
|
1536
1536
|
optionalArgs: ["spaceType", "folderId"],
|
|
1537
1537
|
description:
|
|
1538
1538
|
"Render an HTML string to a PDF server-side, then either OPEN it or SAVE " +
|
|
1539
|
-
"it. `openPdf(html, { fileName? })` is THE way to give the user a PDF " +
|
|
1539
|
+
"it. `openPdf(html, { fileName?, appendFileIds? })` is THE way to give the user a PDF " +
|
|
1540
1540
|
"to view, print, download or share: it renders the HTML and opens the " +
|
|
1541
1541
|
"result — the browser's PDF viewer in a new tab on the web Player, the " +
|
|
1542
1542
|
"OS print / share sheet in the native export — and SAVES NOTHING, so " +
|
|
@@ -1552,19 +1552,27 @@ const HOOKS = [
|
|
|
1552
1552
|
"the end-user's Filestore space. The widget passes the SPACE " +
|
|
1553
1553
|
"(`{ spaceType, folderId? }`); the hook resolves owner_id from the host " +
|
|
1554
1554
|
"context and POSTs JSON through ctx.filestore.files.exportPdf. " +
|
|
1555
|
-
"`exportToPdf(html, { fileName?, folderId? })` resolves to the created " +
|
|
1555
|
+
"`exportToPdf(html, { fileName?, folderId?, appendFileIds? })` resolves to the created " +
|
|
1556
1556
|
"file row (mime_type `application/pdf`) or throws the wire error; " +
|
|
1557
1557
|
"`err.status` 403/404 means the destination denied the add (403 " +
|
|
1558
1558
|
"when the caller can see the position, 404 when it cannot), a 413 " +
|
|
1559
1559
|
"that the rendered " +
|
|
1560
1560
|
"PDF exceeded the size cap. The HTML is rendered by the host's PDF " +
|
|
1561
1561
|
"service (headless Chromium) — the SAME server-side pipeline on the web " +
|
|
1562
|
-
"Player and the native export, so no browser-only PDF library is used."
|
|
1562
|
+
"Player and the native export, so no browser-only PDF library is used. " +
|
|
1563
|
+
"To put a record's attached PDFs INSIDE the document, pass their Filestore " +
|
|
1564
|
+
"file ids as `appendFileIds` (at most 20) to either call: every page of " +
|
|
1565
|
+
"each file follows the rendered HTML, in the order given. Do not try to " +
|
|
1566
|
+
"embed a PDF through an <img>, <iframe> or link. Each id must be a PDF the " +
|
|
1567
|
+
"user may download, or the call rejects naming it in `err.details.file_id`: " +
|
|
1568
|
+
"404 FILE_NOT_FOUND, 403 FILE_DOWNLOAD_NOT_PERMITTED, or 422 " +
|
|
1569
|
+
"PDF_NOT_MERGEABLE (not a PDF, password-protected, or unreadable). On a " +
|
|
1570
|
+
"422, drop that id and call again so the rest of the report still opens.",
|
|
1563
1571
|
returnShape: {
|
|
1564
|
-
openPdf: "(html, { fileName? }) => Promise<void>",
|
|
1572
|
+
openPdf: "(html, { fileName?, appendFileIds? }) => Promise<void>",
|
|
1565
1573
|
opening: "boolean",
|
|
1566
1574
|
canOpenPdf: "boolean",
|
|
1567
|
-
exportToPdf: "(html, { fileName?, folderId? }) => Promise<FilestoreFile>",
|
|
1575
|
+
exportToPdf: "(html, { fileName?, folderId?, appendFileIds? }) => Promise<FilestoreFile>",
|
|
1568
1576
|
exporting: "boolean",
|
|
1569
1577
|
error: "Error | null",
|
|
1570
1578
|
lastExported: "FilestoreFile | null",
|
|
@@ -3956,7 +3964,7 @@ const MANIFEST_SCHEMA = {
|
|
|
3956
3964
|
type: "object",
|
|
3957
3965
|
required: false,
|
|
3958
3966
|
description:
|
|
3959
|
-
"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: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). 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 32 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. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
3967
|
+
"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: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). 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 32 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. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, `@uuid` on UUID (a new random id per row), or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` / `@uuid` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
3960
3968
|
},
|
|
3961
3969
|
translations: {
|
|
3962
3970
|
type: "object",
|
|
@@ -6429,7 +6437,12 @@ const CONTRACT = deepFreeze({
|
|
|
6429
6437
|
// scope. A widget runs one of the workspace's Actions and reads its result.
|
|
6430
6438
|
// 1.213.0: behavioural (sc-13933) — `<Tabs>` wraps tabs that do not fit onto
|
|
6431
6439
|
// another line instead of scrolling them out of sight.
|
|
6432
|
-
|
|
6440
|
+
// 1.214.0: additive (sc-13746) — a datastoreTemplate column may be a
|
|
6441
|
+
// `UUID` (validated, stored lowercase); `"@uuid"` defaults it to a new
|
|
6442
|
+
// random id per row and may be `defaultLocked`.
|
|
6443
|
+
// 1.215.0: additive (sc-13938) — `usePdfExport` `openPdf` / `exportToPdf`
|
|
6444
|
+
// take `appendFileIds`: stored PDFs whose pages follow the rendered HTML.
|
|
6445
|
+
version: "1.215.0",
|
|
6433
6446
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6434
6447
|
hooks: HOOKS,
|
|
6435
6448
|
primitives: PRIMITIVES,
|
package/dist/hooks.js
CHANGED
|
@@ -5171,7 +5171,7 @@ export function usePdfExport(options) {
|
|
|
5171
5171
|
try {
|
|
5172
5172
|
await viewer.open({
|
|
5173
5173
|
fileName: _pdfFileName(overrides && overrides.fileName),
|
|
5174
|
-
load: () => files.renderPdf(
|
|
5174
|
+
load: () => files.renderPdf(_pdfRenderBody(html, overrides)),
|
|
5175
5175
|
});
|
|
5176
5176
|
} catch (err) {
|
|
5177
5177
|
setError(err);
|
|
@@ -5204,6 +5204,7 @@ export function usePdfExport(options) {
|
|
|
5204
5204
|
};
|
|
5205
5205
|
if (folderId) body.folder_id = folderId;
|
|
5206
5206
|
if (fileName) body.file_name = fileName;
|
|
5207
|
+
Object.assign(body, _pdfRenderBody(html, overrides));
|
|
5207
5208
|
setExporting(true);
|
|
5208
5209
|
setError(null);
|
|
5209
5210
|
try {
|
|
@@ -5231,6 +5232,12 @@ export function usePdfExport(options) {
|
|
|
5231
5232
|
};
|
|
5232
5233
|
}
|
|
5233
5234
|
|
|
5235
|
+
// `appendFileIds`: stored PDFs whose pages follow the rendered HTML.
|
|
5236
|
+
function _pdfRenderBody(html, overrides) {
|
|
5237
|
+
const ids = overrides && overrides.appendFileIds;
|
|
5238
|
+
return Array.isArray(ids) && ids.length > 0 ? { html, append_file_ids: ids } : { html };
|
|
5239
|
+
}
|
|
5240
|
+
|
|
5234
5241
|
function _canOpenPdf(viewer) {
|
|
5235
5242
|
return Boolean(
|
|
5236
5243
|
viewer &&
|
package/dist/index.d.ts
CHANGED
|
@@ -185,12 +185,14 @@ export interface WidgetDatastoreTemplateColumn {
|
|
|
185
185
|
| "FLOAT"
|
|
186
186
|
| "BOOL"
|
|
187
187
|
| "DATE"
|
|
188
|
+
| "DATETIME"
|
|
188
189
|
| "FILE"
|
|
189
190
|
| "STRING_ARRAY"
|
|
190
191
|
| "INT_ARRAY"
|
|
191
192
|
| "RELATION"
|
|
192
193
|
| "USER"
|
|
193
|
-
| "USER_GROUP"
|
|
194
|
+
| "USER_GROUP"
|
|
195
|
+
| "UUID";
|
|
194
196
|
required?: boolean;
|
|
195
197
|
/** For RELATION columns only — points at a sibling table's `suffix`. */
|
|
196
198
|
targetSuffix?: string;
|
|
@@ -221,14 +223,14 @@ export interface WidgetDatastoreTemplateColumn {
|
|
|
221
223
|
encrypted?: boolean;
|
|
222
224
|
/**
|
|
223
225
|
* Written when a create omits the column. `"@currentUser"` (USER) stamps
|
|
224
|
-
* the signed-in user
|
|
225
|
-
* time. Other types take a literal of their own type. FILE, RELATION and
|
|
226
|
+
* the signed-in user, `"@now"` (DATE / DATETIME) the current date or
|
|
227
|
+
* time, and `"@uuid"` (UUID) a new random id per row. Other types take a literal of their own type. FILE, RELATION and
|
|
226
228
|
* USER_GROUP columns take none, because ids belong to the installing
|
|
227
229
|
* workspace.
|
|
228
230
|
*/
|
|
229
231
|
defaultValue?: unknown;
|
|
230
232
|
/**
|
|
231
|
-
* With a `"@currentUser"` or `"@
|
|
233
|
+
* With a `"@currentUser"`, `"@now"` or `"@uuid"` default only: the server always
|
|
232
234
|
* stamps it. Any value a client sends for the column is ignored on create
|
|
233
235
|
* and on update, so an author column cannot be forged.
|
|
234
236
|
*/
|
|
@@ -1633,12 +1635,14 @@ export interface DatastoreSchemaColumn {
|
|
|
1633
1635
|
| "FLOAT"
|
|
1634
1636
|
| "BOOL"
|
|
1635
1637
|
| "DATE"
|
|
1638
|
+
| "DATETIME"
|
|
1636
1639
|
| "FILE"
|
|
1637
1640
|
| "STRING_ARRAY"
|
|
1638
1641
|
| "INT_ARRAY"
|
|
1639
1642
|
| "RELATION"
|
|
1640
1643
|
| "USER"
|
|
1641
|
-
| "USER_GROUP"
|
|
1644
|
+
| "USER_GROUP"
|
|
1645
|
+
| "UUID";
|
|
1642
1646
|
required: boolean;
|
|
1643
1647
|
/** For RELATION columns only. */
|
|
1644
1648
|
relation_type?: "ONE_TO_ONE" | "ONE_TO_MANY" | "MANY_TO_MANY" | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.265.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",
|
package/src/contract.js
CHANGED
|
@@ -1536,7 +1536,7 @@ const HOOKS = [
|
|
|
1536
1536
|
optionalArgs: ["spaceType", "folderId"],
|
|
1537
1537
|
description:
|
|
1538
1538
|
"Render an HTML string to a PDF server-side, then either OPEN it or SAVE " +
|
|
1539
|
-
"it. `openPdf(html, { fileName? })` is THE way to give the user a PDF " +
|
|
1539
|
+
"it. `openPdf(html, { fileName?, appendFileIds? })` is THE way to give the user a PDF " +
|
|
1540
1540
|
"to view, print, download or share: it renders the HTML and opens the " +
|
|
1541
1541
|
"result — the browser's PDF viewer in a new tab on the web Player, the " +
|
|
1542
1542
|
"OS print / share sheet in the native export — and SAVES NOTHING, so " +
|
|
@@ -1552,19 +1552,27 @@ const HOOKS = [
|
|
|
1552
1552
|
"the end-user's Filestore space. The widget passes the SPACE " +
|
|
1553
1553
|
"(`{ spaceType, folderId? }`); the hook resolves owner_id from the host " +
|
|
1554
1554
|
"context and POSTs JSON through ctx.filestore.files.exportPdf. " +
|
|
1555
|
-
"`exportToPdf(html, { fileName?, folderId? })` resolves to the created " +
|
|
1555
|
+
"`exportToPdf(html, { fileName?, folderId?, appendFileIds? })` resolves to the created " +
|
|
1556
1556
|
"file row (mime_type `application/pdf`) or throws the wire error; " +
|
|
1557
1557
|
"`err.status` 403/404 means the destination denied the add (403 " +
|
|
1558
1558
|
"when the caller can see the position, 404 when it cannot), a 413 " +
|
|
1559
1559
|
"that the rendered " +
|
|
1560
1560
|
"PDF exceeded the size cap. The HTML is rendered by the host's PDF " +
|
|
1561
1561
|
"service (headless Chromium) — the SAME server-side pipeline on the web " +
|
|
1562
|
-
"Player and the native export, so no browser-only PDF library is used."
|
|
1562
|
+
"Player and the native export, so no browser-only PDF library is used. " +
|
|
1563
|
+
"To put a record's attached PDFs INSIDE the document, pass their Filestore " +
|
|
1564
|
+
"file ids as `appendFileIds` (at most 20) to either call: every page of " +
|
|
1565
|
+
"each file follows the rendered HTML, in the order given. Do not try to " +
|
|
1566
|
+
"embed a PDF through an <img>, <iframe> or link. Each id must be a PDF the " +
|
|
1567
|
+
"user may download, or the call rejects naming it in `err.details.file_id`: " +
|
|
1568
|
+
"404 FILE_NOT_FOUND, 403 FILE_DOWNLOAD_NOT_PERMITTED, or 422 " +
|
|
1569
|
+
"PDF_NOT_MERGEABLE (not a PDF, password-protected, or unreadable). On a " +
|
|
1570
|
+
"422, drop that id and call again so the rest of the report still opens.",
|
|
1563
1571
|
returnShape: {
|
|
1564
|
-
openPdf: "(html, { fileName? }) => Promise<void>",
|
|
1572
|
+
openPdf: "(html, { fileName?, appendFileIds? }) => Promise<void>",
|
|
1565
1573
|
opening: "boolean",
|
|
1566
1574
|
canOpenPdf: "boolean",
|
|
1567
|
-
exportToPdf: "(html, { fileName?, folderId? }) => Promise<FilestoreFile>",
|
|
1575
|
+
exportToPdf: "(html, { fileName?, folderId?, appendFileIds? }) => Promise<FilestoreFile>",
|
|
1568
1576
|
exporting: "boolean",
|
|
1569
1577
|
error: "Error | null",
|
|
1570
1578
|
lastExported: "FilestoreFile | null",
|
|
@@ -3956,7 +3964,7 @@ const MANIFEST_SCHEMA = {
|
|
|
3956
3964
|
type: "object",
|
|
3957
3965
|
required: false,
|
|
3958
3966
|
description:
|
|
3959
|
-
"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: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). 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 32 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. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
3967
|
+
"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: 100 tables, 200 columns per table, 100 sample rows per table and 20000 sample cells across the whole template (each row counts the keys it sets, and at least one, summed over every table). RELATION columns address siblings by `targetSuffix` (any table in the same array, in any order; tables may relate to each other both ways). 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 32 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. An `inheritAcl` RELATION column may add `inheritAclReadOnly: true` — rows then inherit READ access only, so members of the parent read every child and may add children, but change or delete only the rows they created (pair with `grantCreatorPermissions`). A column may declare `defaultValue`, written when a create omits it: `@currentUser` on USER, `@now` on DATE / DATETIME, `@uuid` on UUID (a new random id per row), or a literal of the column's own type (none on FILE, RELATION or USER_GROUP — ids belong to the installing workspace). `defaultLocked: true` on an `@currentUser` / `@now` / `@uuid` default makes the server always stamp it and ignore any client value on create and update, so an author column cannot be forged. `immutable: true` makes a column write-once: a create sets it as usual and any value sent for it on update is ignored (not valid on a MANY_TO_MANY relation). A table may declare `presence: true` so the people who share a row see which of each other are online (useOnlineMembers); it takes effect only while the workspace records user activity, and an upgrade that declares it turns it on for the existing table. A ONE_TO_ONE / ONE_TO_MANY RELATION may declare `latestVia: \"<relation column on its target that points back>\"` (onDelete left at SET_NULL; that relation must have inheritAcl and be immutable): the server keeps it at the newest row of the target table that points back at this row. A column may declare `copyOf: { via, column }`: the server keeps it equal to `column` on the row the latest-row column `via` points at (same type, or text into text, cut to 140 characters in a STRING column; a copy of an encrypted column must be encrypted, and a copy cannot inheritAcl). Both are written only by the server, in the same write as the related row; a client value is ignored. A table may declare `constraints`, checked on every write for every client and refused as CONSTRAINT_VIOLATION: `unique` ({ columns, partitionColumns?, unlessRelated?: { relation, column } } — waived for rows whose related row has that yes/no column set), `no-overlap` ({ startColumn, endColumn, partitionColumns? }), `related-open` ({ relation, closedColumn } — no row is added, changed or removed while the related row's column is filled) and `related-index` ({ column, relation, listColumn } — the number must be a position in the related row's list). An upgrade adds a declared constraint to an existing table without judging the rows already in it.",
|
|
3960
3968
|
},
|
|
3961
3969
|
translations: {
|
|
3962
3970
|
type: "object",
|
|
@@ -6429,7 +6437,12 @@ const CONTRACT = deepFreeze({
|
|
|
6429
6437
|
// scope. A widget runs one of the workspace's Actions and reads its result.
|
|
6430
6438
|
// 1.213.0: behavioural (sc-13933) — `<Tabs>` wraps tabs that do not fit onto
|
|
6431
6439
|
// another line instead of scrolling them out of sight.
|
|
6432
|
-
|
|
6440
|
+
// 1.214.0: additive (sc-13746) — a datastoreTemplate column may be a
|
|
6441
|
+
// `UUID` (validated, stored lowercase); `"@uuid"` defaults it to a new
|
|
6442
|
+
// random id per row and may be `defaultLocked`.
|
|
6443
|
+
// 1.215.0: additive (sc-13938) — `usePdfExport` `openPdf` / `exportToPdf`
|
|
6444
|
+
// take `appendFileIds`: stored PDFs whose pages follow the rendered HTML.
|
|
6445
|
+
version: "1.215.0",
|
|
6433
6446
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6434
6447
|
hooks: HOOKS,
|
|
6435
6448
|
primitives: PRIMITIVES,
|