@colixsystems/widget-sdk 0.239.0 → 0.240.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 +8 -4
- package/dist/contract.cjs +4 -2
- package/dist/contract.js +4 -2
- package/dist/index.d.ts +11 -6
- package/package.json +1 -1
- package/src/contract.js +4 -2
package/README.md
CHANGED
|
@@ -84,7 +84,11 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
84
84
|
|
|
85
85
|
## Status
|
|
86
86
|
|
|
87
|
-
`v0.
|
|
87
|
+
`v0.240.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**.
|
|
88
|
+
|
|
89
|
+
### What's new in 0.240.0 (contract 1.194.0)
|
|
90
|
+
|
|
91
|
+
**A `datastoreTemplate` can describe a whole system.** A template may now declare up to 100 tables of up to 200 columns, up to 100 sample `rows` per table and up to 32 `roles`. Sample rows have one more bound: the whole template seeds at most 20000 cells, where each row counts the keys it sets and at least one, summed over every row of every table. A template past any limit is refused at publish with a message naming the limit and the count. Every template valid under 0.239.0 is still valid.
|
|
88
92
|
|
|
89
93
|
### What's new in 0.239.0 (contract 1.193.0)
|
|
90
94
|
|
|
@@ -1601,7 +1605,7 @@ datastoreTemplate: {
|
|
|
1601
1605
|
|
|
1602
1606
|
`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.
|
|
1603
1607
|
|
|
1604
|
-
- **`key`** is lowercase-kebab (`/^[a-z][a-z0-9-]*$/`)
|
|
1608
|
+
- **`key`** is lowercase-kebab (`/^[a-z][a-z0-9-]*$/`); the roles cap is in `WidgetDatastoreTemplate`. 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.
|
|
1605
1609
|
- **`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.
|
|
1606
1610
|
- **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.
|
|
1607
1611
|
- **Purely additive.** A template declaring neither `roles` nor `roleGrants` behaves exactly as it does today.
|
|
@@ -2574,7 +2578,7 @@ useEffect(() => {
|
|
|
2574
2578
|
|
|
2575
2579
|
### What's new in 0.51.0
|
|
2576
2580
|
|
|
2577
|
-
**`datastoreTemplate` tables can ship sample `rows` (sc-2070).** Each `WidgetDatastoreTemplateTable` now takes an optional `rows` array — sample data seeded into the table at install time so the widget renders with real content instead of an empty state. Each entry is an object keyed by column `name`; only the scalar/array column types are seedable (`STRING`, `TEXT`, `NUMBER`, `FLOAT`, `BOOL`, `DATE`, `STRING_ARRAY`, `INT_ARRAY`). RELATION, FILE, USER, and USER_GROUP columns are rejected at validation time (a sample row has no way to express their ids). A `null` value skips that cell;
|
|
2581
|
+
**`datastoreTemplate` tables can ship sample `rows` (sc-2070).** Each `WidgetDatastoreTemplateTable` now takes an optional `rows` array — sample data seeded into the table at install time so the widget renders with real content instead of an empty state. Each entry is an object keyed by column `name`; only the scalar/array column types are seedable (`STRING`, `TEXT`, `NUMBER`, `FLOAT`, `BOOL`, `DATE`, `STRING_ARRAY`, `INT_ARRAY`). RELATION, FILE, USER, and USER_GROUP columns are rejected at validation time (a sample row has no way to express their ids). A `null` value skips that cell; the per-table row cap is on `WidgetDatastoreTemplateTable.rows`. The "Seed data" action and every install path seed these rows in the same transaction that creates the tables. Additive — `rows` is optional and existing templates that omit it behave exactly as before.
|
|
2578
2582
|
|
|
2579
2583
|
### What's new in 0.49.0
|
|
2580
2584
|
|
|
@@ -2939,7 +2943,7 @@ The "split-implementation + vetted package list" pivot.
|
|
|
2939
2943
|
|
|
2940
2944
|
### What was in 0.3.0
|
|
2941
2945
|
|
|
2942
|
-
Additive: `WidgetManifest` carries an optional `datastoreTemplate` field. When a tenant installs a widget that declares one, the table set is seeded into their workspace alongside the install. Tables follow the built-in template semantics (auto-suffixed naming, creator-grants, public grants, cross-relation inheritance) and persist when the widget is later uninstalled. See `WidgetDatastoreTemplate` in `src/index.d.ts` for the
|
|
2946
|
+
Additive: `WidgetManifest` carries an optional `datastoreTemplate` field. When a tenant installs a widget that declares one, the table set is seeded into their workspace alongside the install. Tables follow the built-in template semantics (auto-suffixed naming, creator-grants, public grants, cross-relation inheritance) and persist when the widget is later uninstalled. See `WidgetDatastoreTemplate` in `src/index.d.ts` for the current limits; RELATION columns address siblings by `suffix`.
|
|
2943
2947
|
|
|
2944
2948
|
## Public API
|
|
2945
2949
|
|
package/dist/contract.cjs
CHANGED
|
@@ -3690,7 +3690,7 @@ const MANIFEST_SCHEMA = {
|
|
|
3690
3690
|
type: "object",
|
|
3691
3691
|
required: false,
|
|
3692
3692
|
description:
|
|
3693
|
-
"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:
|
|
3693
|
+
"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.",
|
|
3694
3694
|
},
|
|
3695
3695
|
translations: {
|
|
3696
3696
|
type: "object",
|
|
@@ -6090,7 +6090,9 @@ const CONTRACT = deepFreeze({
|
|
|
6090
6090
|
// 1.193.0: additive (sc-11821) — `useNavigation().scrollToSection(id)`
|
|
6091
6091
|
// scrolls the page to one of its sections on both hosts, and the
|
|
6092
6092
|
// `sectionRef` propertySchema type gives the author a section picker.
|
|
6093
|
-
|
|
6093
|
+
// 1.194.0: more permissive (sc-11945) — a datastoreTemplate takes 100
|
|
6094
|
+
// tables, 200 columns, 100 rows per table, 20000 cells and 32 roles.
|
|
6095
|
+
version: "1.194.0",
|
|
6094
6096
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6095
6097
|
hooks: HOOKS,
|
|
6096
6098
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -3686,7 +3686,7 @@ const MANIFEST_SCHEMA = {
|
|
|
3686
3686
|
type: "object",
|
|
3687
3687
|
required: false,
|
|
3688
3688
|
description:
|
|
3689
|
-
"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:
|
|
3689
|
+
"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.",
|
|
3690
3690
|
},
|
|
3691
3691
|
translations: {
|
|
3692
3692
|
type: "object",
|
|
@@ -6086,7 +6086,9 @@ const CONTRACT = deepFreeze({
|
|
|
6086
6086
|
// 1.193.0: additive (sc-11821) — `useNavigation().scrollToSection(id)`
|
|
6087
6087
|
// scrolls the page to one of its sections on both hosts, and the
|
|
6088
6088
|
// `sectionRef` propertySchema type gives the author a section picker.
|
|
6089
|
-
|
|
6089
|
+
// 1.194.0: more permissive (sc-11945) — a datastoreTemplate takes 100
|
|
6090
|
+
// tables, 200 columns, 100 rows per table, 20000 cells and 32 roles.
|
|
6091
|
+
version: "1.194.0",
|
|
6090
6092
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6091
6093
|
hooks: HOOKS,
|
|
6092
6094
|
primitives: PRIMITIVES,
|
package/dist/index.d.ts
CHANGED
|
@@ -155,12 +155,15 @@ export interface WidgetInputDescriptor {
|
|
|
155
155
|
* tenant may have authored records into them.
|
|
156
156
|
*
|
|
157
157
|
* The shape mirrors the built-in template registry on the backend, with
|
|
158
|
-
*
|
|
159
|
-
*
|
|
158
|
+
* limits a third-party widget must satisfy (enforced at submission time by
|
|
159
|
+
* the static analyzer):
|
|
160
160
|
*
|
|
161
|
-
* - At most
|
|
162
|
-
*
|
|
163
|
-
* - At most
|
|
161
|
+
* - At most 100 tables per widget.
|
|
162
|
+
* - At most 200 columns per table.
|
|
163
|
+
* - At most 100 sample rows per table, and 20000 sample cells across the
|
|
164
|
+
* whole template (each row counts the keys it sets, and at least one,
|
|
165
|
+
* summed over every table).
|
|
166
|
+
* - At most 32 roles.
|
|
164
167
|
*
|
|
165
168
|
* RELATION columns reference siblings within the same template via
|
|
166
169
|
* `targetSuffix` — that suffix MUST belong to a table in the same array,
|
|
@@ -316,7 +319,9 @@ export interface WidgetDatastoreTemplateTable {
|
|
|
316
319
|
* Optional sample rows seeded into the table at install time so the widget
|
|
317
320
|
* renders with real data instead of an empty state. Each key is a column
|
|
318
321
|
* `name`; only non-relation/file/user columns are seedable (RELATION, FILE,
|
|
319
|
-
* USER, USER_GROUP are rejected). `null` skips that cell. Max
|
|
322
|
+
* USER, USER_GROUP are rejected). `null` skips that cell. Max 100 rows, and
|
|
323
|
+
* each row counts the keys it sets, and at least one, toward the
|
|
324
|
+
* template's 20000-cell budget.
|
|
320
325
|
*/
|
|
321
326
|
rows?: Array<
|
|
322
327
|
Record<string, string | number | boolean | string[] | number[] | null>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.240.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
|
@@ -3686,7 +3686,7 @@ const MANIFEST_SCHEMA = {
|
|
|
3686
3686
|
type: "object",
|
|
3687
3687
|
required: false,
|
|
3688
3688
|
description:
|
|
3689
|
-
"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:
|
|
3689
|
+
"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.",
|
|
3690
3690
|
},
|
|
3691
3691
|
translations: {
|
|
3692
3692
|
type: "object",
|
|
@@ -6086,7 +6086,9 @@ const CONTRACT = deepFreeze({
|
|
|
6086
6086
|
// 1.193.0: additive (sc-11821) — `useNavigation().scrollToSection(id)`
|
|
6087
6087
|
// scrolls the page to one of its sections on both hosts, and the
|
|
6088
6088
|
// `sectionRef` propertySchema type gives the author a section picker.
|
|
6089
|
-
|
|
6089
|
+
// 1.194.0: more permissive (sc-11945) — a datastoreTemplate takes 100
|
|
6090
|
+
// tables, 200 columns, 100 rows per table, 20000 cells and 32 roles.
|
|
6091
|
+
version: "1.194.0",
|
|
6090
6092
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6091
6093
|
hooks: HOOKS,
|
|
6092
6094
|
primitives: PRIMITIVES,
|