@abeedoo/radish-schemas 1.10.0 → 1.12.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/index.js +1 -1
- package/package.json +1 -1
- package/prompts/radish-types-generation.md +32 -6
- package/schemas/types.schema.json +14 -1
package/index.js
CHANGED
|
@@ -31,7 +31,7 @@ export {
|
|
|
31
31
|
* @property {string} minCliVersion - Minimum radish-cli version compatible with this package
|
|
32
32
|
*/
|
|
33
33
|
export const VERSIONING = {
|
|
34
|
-
packageVersion: '1.
|
|
34
|
+
packageVersion: '1.12.0',
|
|
35
35
|
currentSpecVersion: 1,
|
|
36
36
|
supportedSpecVersions: [1],
|
|
37
37
|
minCliVersion: '0.1.0'
|
package/package.json
CHANGED
|
@@ -127,7 +127,32 @@ The following entities are already provided by the system. DO NOT recreate them
|
|
|
127
127
|
```
|
|
128
128
|
`matchUserField` supports array match — if `user.orgIds` is an array, checks if `record.orgId` is in that array.
|
|
129
129
|
|
|
130
|
-
9. **
|
|
130
|
+
9. **Tenancy** (optional): If the app is multi-tenant — every record belongs to an organization, workspace, clinic, or similar — mark that one entity with `"tenant": true`, then declare only the exceptions:
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"Org": { "plural": "orgs", "tenant": true, "fields": { ... } },
|
|
134
|
+
"User": { "plural": "users", "fields": { "orgIds": { "type": "objectId[]", "ref": "Org" } } },
|
|
135
|
+
"Invoice": { "plural": "invoices", "fields": { "orgId": { "type": "objectId", "ref": "Org", "required": true } } },
|
|
136
|
+
"Setting": { "plural": "settings", "tenancy": "optional", "fields": { "orgId": { "type": "objectId", "ref": "Org" } } },
|
|
137
|
+
"AuditLog": { "plural": "auditLogs", "tenancy": "none", "fields": { "orgId": { "type": "objectId", "ref": "Org" } } }
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
Two separate keys, because they answer questions about different entities:
|
|
141
|
+
|
|
142
|
+
- **`"tenant": true`** — on the boundary entity itself, and at most one entity per blueprint. `true` is the only valid value; omit the key everywhere else. Nothing is partitioned until some entity declares it. Once declared, every entity with a ref to it is confined automatically — no per-entity declaration needed. `User` must have exactly one field ref-ing it (`objectId[]` if a user can belong to several tenants, `objectId` for exactly one); that field is discovered as the membership field.
|
|
143
|
+
- **`"tenancy"`** — on a *member* entity, to record an exception:
|
|
144
|
+
- **absent** (the default): strictly partitioned if it refs the boundary entity. This is the safe state — a forgotten key confines rather than exposes.
|
|
145
|
+
- **`"optional"`**: partitioned, but rows with NO tenant are visible to everyone — a shared default with per-tenant overrides. Use for settings, templates, categories, feature flags.
|
|
146
|
+
- **`"none"`**: never confined despite ref-ing the boundary — a tenant id kept for provenance rather than ownership, such as an audit row naming the org an action occurred in.
|
|
147
|
+
- **Omit both** on ordinary entities, including reference data with no ref to the boundary — those are global by construction, since there is nothing to partition on.
|
|
148
|
+
|
|
149
|
+
Do not guess at any of this:
|
|
150
|
+
- A tenant and a group look identical in a blueprint (an entity, a membership field, an id on the records they cover), but a tenant **confines** results while a group **widens** them. Set `"tenant": true` only when the description actually describes tenant isolation; if it describes teams, clans, or groups that share access, leave these keys off and model it with `scope` instead.
|
|
151
|
+
- Never infer `"optional"` from the ref field being non-required. A nullable tenant ref means "shared with everyone" in one app and "not backfilled yet, keep hidden" in another — identical shape, opposite intent. Declare `"optional"` only when the description says the rows are genuinely shared.
|
|
152
|
+
|
|
153
|
+
Note that `ownership` is a different axis: it says who owns rows (`"user"` or `"system"`), not what partitions them.
|
|
154
|
+
|
|
155
|
+
10. **Enhanced Enums**: Use key-value pairs for better UX:
|
|
131
156
|
```json
|
|
132
157
|
{
|
|
133
158
|
"type": "enum",
|
|
@@ -139,9 +164,9 @@ The following entities are already provided by the system. DO NOT recreate them
|
|
|
139
164
|
}
|
|
140
165
|
```
|
|
141
166
|
|
|
142
|
-
|
|
167
|
+
11. **Performance**: Add `filters` array for searchable fields and `indexes` for performance
|
|
143
168
|
|
|
144
|
-
|
|
169
|
+
12. **Nested Objects**: Use `"type": "object"` with `"fields"` for nested structures:
|
|
145
170
|
```json
|
|
146
171
|
{
|
|
147
172
|
"type": "object",
|
|
@@ -153,7 +178,7 @@ The following entities are already provided by the system. DO NOT recreate them
|
|
|
153
178
|
}
|
|
154
179
|
```
|
|
155
180
|
|
|
156
|
-
|
|
181
|
+
13. **Search Index** (optional): Enable full-text search with engine-specific adapters:
|
|
157
182
|
```json
|
|
158
183
|
{
|
|
159
184
|
"Product": {
|
|
@@ -188,6 +213,7 @@ The following entities are already provided by the system. DO NOT recreate them
|
|
|
188
213
|
- **sync**: `inline` (immediate) or `background` (via jobs)
|
|
189
214
|
- **vector**: Enable vector/embedding search with `sourceFields` to generate embeddings from
|
|
190
215
|
- **searchable**: Each entry is either a plain field name or `{ "field": "name", "stem": true }`. Stemming reduces terms to their word stems at index time (`computers` → `computer`), so a plural query matches singular text as an exact match rather than relying on typo correction. Set `stem: true` only on prose fields (name, description, summary, body). Never stem identifiers (`sku`, `slug`, `code`), brand or category values, anything listed under `filterable`/`facetable`, or non-string fields — stemming a facet value breaks the filter that reads it.
|
|
216
|
+
- **indexFilter**: A MongoDB-style query object controlling which documents belong in the index, e.g. `{ "status": { "$ne": "discontinued" } }`. Note the delete semantics: it is applied on every create/update sync, and a document that stops matching is **deleted** from the search engine, not merely skipped — a product flipped to `discontinued` disappears from search immediately. Use it for excluding archived, draft, or discontinued records; do not use it for anything a user should be able to filter on themselves (that is `filterable`).
|
|
191
217
|
- **configureBy**: `"rows"` marks an entity whose *records* are vocabulary for other entities' search indexes — each row (or group of rows) becomes a candidate field in the consuming entity's search configuration. Use it for entities that define terminology rather than content: `Attribute`, `Term`, `Tag`, `Taxonomy`. The system discovers code/label fields and grouping refs from the entity's own field definitions, so no extra configuration is needed. A vocabulary entity that is not itself searchable needs only `"search": { "configureBy": "rows" }`; combine with `"enabled": true` when the entity should also have its own index:
|
|
192
218
|
```json
|
|
193
219
|
"Term": {
|
|
@@ -204,7 +230,7 @@ The following entities are already provided by the system. DO NOT recreate them
|
|
|
204
230
|
}
|
|
205
231
|
```
|
|
206
232
|
|
|
207
|
-
|
|
233
|
+
14. **Expiration / TTL** (optional): For entities whose records should be purged automatically (sessions, tokens, logs, temporary uploads), use the entity-level `ttl` shorthand:
|
|
208
234
|
```json
|
|
209
235
|
{
|
|
210
236
|
"Session": {
|
|
@@ -224,7 +250,7 @@ The following entities are already provided by the system. DO NOT recreate them
|
|
|
224
250
|
]
|
|
225
251
|
```
|
|
226
252
|
|
|
227
|
-
|
|
253
|
+
15. **Automatic Fields** (DO NOT add these manually):
|
|
228
254
|
- When `"defaults": { "timestamps": true }` is set, `createdAt` and `updatedAt` are added automatically
|
|
229
255
|
- When `"defaults": { "owned": true }` is set, `ownerId` is added automatically
|
|
230
256
|
- Adding these fields manually causes duplication
|
|
@@ -55,9 +55,18 @@
|
|
|
55
55
|
},
|
|
56
56
|
"ownership": {
|
|
57
57
|
"type": "string",
|
|
58
|
-
"enum": ["user", "system"
|
|
58
|
+
"enum": ["user", "system"],
|
|
59
59
|
"default": "user"
|
|
60
60
|
},
|
|
61
|
+
"tenant": {
|
|
62
|
+
"const": true,
|
|
63
|
+
"description": "This entity is the tenancy boundary (Org, Workspace, Clinic). Nothing is partitioned until exactly one entity declares this. Every entity with a ref to it is then confined to the caller's tenants. Only true is valid — omit the key on every other entity. Unrelated to ownership, which says who owns rows rather than what partitions them."
|
|
64
|
+
},
|
|
65
|
+
"tenancy": {
|
|
66
|
+
"type": "string",
|
|
67
|
+
"enum": ["optional", "none"],
|
|
68
|
+
"description": "How tenancy applies to THIS entity. Absent (the default): strictly partitioned, if it refs the boundary entity. \"optional\": partitioned, but rows with NO tenant are visible to everyone — a shared default with per-tenant overrides. \"none\": never confined, even though it refs the boundary (a tenant id kept for provenance, not ownership)."
|
|
69
|
+
},
|
|
61
70
|
"extends": {
|
|
62
71
|
"type": "string",
|
|
63
72
|
"description": "Base type to extend from. Can be EntityBase, SystemEntityBase, ContentBase, or another entity name.",
|
|
@@ -177,6 +186,10 @@
|
|
|
177
186
|
"type": "string",
|
|
178
187
|
"enum": ["rows"],
|
|
179
188
|
"description": "How this entity participates in search configuration. 'rows' means this entity's records are vocabulary for other entities' indexes — each row (or group of rows) becomes a candidate field in the consumer's search config UI. The system discovers code/label fields and grouping refs from the entity's own field definitions. Combinable with enabled: true for entities that are both searchable and vocabulary sources (e.g. Term)."
|
|
189
|
+
},
|
|
190
|
+
"indexFilter": {
|
|
191
|
+
"type": "object",
|
|
192
|
+
"description": "MongoDB-style query object controlling index membership. Documents matching are upserted to the search engine; documents NOT matching are DELETED from it. Applied on every create/update sync — a product flipped to discontinued is removed, not skipped. Example: { \"status\": { \"$ne\": \"discontinued\" } }"
|
|
180
193
|
}
|
|
181
194
|
}
|
|
182
195
|
},
|