@stndrds/cli 1.0.0-alpha.258 → 1.0.0-alpha.259
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/dist/assets/standards-drift.yml +242 -0
- package/dist/assets/standards-pull-skill.md +101 -0
- package/dist/bin.mjs +753 -213
- package/package.json +5 -2
- package/dist/assets/schema-builder-reference.md +0 -513
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stndrds/cli",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.259",
|
|
4
4
|
"description": "CLI tool to interact with Standards API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
"dist"
|
|
11
11
|
],
|
|
12
12
|
"dependencies": {
|
|
13
|
+
"@clack/prompts": "^1.7.0",
|
|
13
14
|
"chalk": "^5.4.1",
|
|
14
15
|
"cli-table3": "^0.6.5",
|
|
15
|
-
"commander": "^13.1.0"
|
|
16
|
+
"commander": "^13.1.0",
|
|
17
|
+
"esbuild": "^0.25.0",
|
|
18
|
+
"@stndrds/schema": "1.0.0-alpha.259"
|
|
16
19
|
},
|
|
17
20
|
"devDependencies": {
|
|
18
21
|
"@types/node": "^25.6.0",
|
|
@@ -1,513 +0,0 @@
|
|
|
1
|
-
# Standards Schema Builder — Reference
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
Standards uses a fluent builder API to define your data schema in TypeScript.
|
|
6
|
-
All objects and attributes declared in code are **system** (immutable at runtime).
|
|
7
|
-
Users can extend your schema at runtime by adding custom attributes via the UI.
|
|
8
|
-
|
|
9
|
-
## Importing
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
import {
|
|
13
|
-
object,
|
|
14
|
-
text, richtext,
|
|
15
|
-
number, currency,
|
|
16
|
-
checkbox,
|
|
17
|
-
date,
|
|
18
|
-
phone,
|
|
19
|
-
select, multiselect, status,
|
|
20
|
-
relation,
|
|
21
|
-
user,
|
|
22
|
-
file, document,
|
|
23
|
-
location,
|
|
24
|
-
formula, rollup,
|
|
25
|
-
} from "@stndrds/schema";
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## `object()` — Define an Object (table)
|
|
29
|
-
|
|
30
|
-
```typescript
|
|
31
|
-
const DEAL = object({ name: "deals", label: "Deal" })
|
|
32
|
-
.pluralLabel("Deals")
|
|
33
|
-
.description("Sales opportunities")
|
|
34
|
-
.icon("briefcase")
|
|
35
|
-
.order(10) // sidebar display order
|
|
36
|
-
.labelExpression("{{ name }}") // REQUIRED — how records display
|
|
37
|
-
.attribute(text({ name: "name", label: "Name" }).required())
|
|
38
|
-
.attribute(number({ name: "amount", label: "Amount" }))
|
|
39
|
-
.build();
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Object names must be **kebab-case** (e.g. `"deals"`, `"order-items"`). Max 63 characters.
|
|
43
|
-
|
|
44
|
-
The `.build()` call is required at the end. The returned value is the `ObjectDefinition`.
|
|
45
|
-
|
|
46
|
-
### `.sealed()` — Prevent custom attributes
|
|
47
|
-
|
|
48
|
-
Marks this object as closed: `standards diff` will error if any user-created
|
|
49
|
-
custom attribute is found in the DB that is not listed in `.tolerate()`.
|
|
50
|
-
|
|
51
|
-
```typescript
|
|
52
|
-
object({ name: "invoice", label: "Invoice" })
|
|
53
|
-
.sealed()
|
|
54
|
-
.labelExpression("{{ ref }}")
|
|
55
|
-
.attribute(number({ name: "amount", label: "Amount" }))
|
|
56
|
-
.build();
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### `.tolerate(["name"])` — Acknowledge known custom attributes
|
|
60
|
-
|
|
61
|
-
On a **sealed** object: these names won't cause a CI failure.
|
|
62
|
-
On an **extensible** object: purely documentary.
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
object({ name: "invoice", label: "Invoice" })
|
|
66
|
-
.sealed()
|
|
67
|
-
.tolerate(["legacy_ref", "old_notes"])
|
|
68
|
-
.labelExpression("{{ ref }}")
|
|
69
|
-
.attribute(number({ name: "amount", label: "Amount" }))
|
|
70
|
-
.build();
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### `.labelExpression()` — Display template (REQUIRED)
|
|
74
|
-
|
|
75
|
-
Every object must declare a label expression. Uses `{{ attribute }}` syntax.
|
|
76
|
-
Pipes are supported: `{{ name | UPPER }}`, `{{ name | capitalize }}`, `{{ name | trim }}`, `{{ name | LOWER }}`.
|
|
77
|
-
|
|
78
|
-
```typescript
|
|
79
|
-
.labelExpression("{{ firstName }} {{ lastName }}")
|
|
80
|
-
.labelExpression("{{ name | UPPER }}")
|
|
81
|
-
.labelExpression("{{ ref }} — {{ title }}")
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### Other object methods
|
|
85
|
-
|
|
86
|
-
| Method | Purpose |
|
|
87
|
-
|---|---|
|
|
88
|
-
| `.pluralLabel("Deals")` | Plural form shown in collections |
|
|
89
|
-
| `.description("...")` | Shown in admin UI tooltips |
|
|
90
|
-
| `.icon("briefcase")` | Icon name from `@stndrds/constants` |
|
|
91
|
-
| `.order(10)` | Sidebar sort order (lower = first) |
|
|
92
|
-
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
## Attribute Types
|
|
96
|
-
|
|
97
|
-
All attribute builders accept `{ name, label }` as first argument.
|
|
98
|
-
`name` must be **camelCase** or **snake_case**, unique within the object.
|
|
99
|
-
|
|
100
|
-
### `text()` — Plain text
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
text({ name: "name", label: "Name" })
|
|
104
|
-
.required()
|
|
105
|
-
.minLength(1)
|
|
106
|
-
.maxLength(255)
|
|
107
|
-
.pattern("^[A-Z].*") // custom regex
|
|
108
|
-
.email() // shortcut: validates as email
|
|
109
|
-
.url() // shortcut: validates as URL
|
|
110
|
-
.slug() // shortcut: validates as slug (a-z0-9-)
|
|
111
|
-
.placeholder("Enter name...")
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Use `.multiline()` for longer plain text:
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
text({ name: "notes", label: "Notes" })
|
|
118
|
-
.multiline()
|
|
119
|
-
.optional()
|
|
120
|
-
.placeholder("Add notes...")
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### `richtext()` — Rich text (Tiptap editor)
|
|
124
|
-
|
|
125
|
-
```typescript
|
|
126
|
-
richtext({ name: "description", label: "Description" }).required()
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### `number()` — Numeric value
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
number({ name: "amount", label: "Amount" })
|
|
133
|
-
.decimal(2) // decimal with 2 places (default unit="decimal")
|
|
134
|
-
.integer() // whole numbers only (unit="integer", decimals=0)
|
|
135
|
-
.percentage() // percentage display (unit="percentage")
|
|
136
|
-
.min(0)
|
|
137
|
-
.max(100)
|
|
138
|
-
.required()
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
Use `.renderAs("rating")` for rating UI backed by a number:
|
|
142
|
-
|
|
143
|
-
```typescript
|
|
144
|
-
number({ name: "score", label: "Score" })
|
|
145
|
-
.renderAs("rating")
|
|
146
|
-
.max(5)
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
### `currency()` — Monetary amount with currency code
|
|
150
|
-
|
|
151
|
-
```typescript
|
|
152
|
-
currency({ name: "price", label: "Price" })
|
|
153
|
-
.defaultCurrency("EUR")
|
|
154
|
-
.allowedCurrencies(["EUR", "USD", "GBP"])
|
|
155
|
-
.allowNegative()
|
|
156
|
-
.required()
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### `checkbox()` — Boolean toggle
|
|
160
|
-
|
|
161
|
-
```typescript
|
|
162
|
-
checkbox({ name: "isVerified", label: "Verified" })
|
|
163
|
-
.defaultValue(false)
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### `date()` — Date picker
|
|
167
|
-
|
|
168
|
-
```typescript
|
|
169
|
-
date({ name: "closeDate", label: "Close Date" })
|
|
170
|
-
.format("short") // "short" | "long" | "full" | "relative"
|
|
171
|
-
.minDate("2024-01-01")
|
|
172
|
-
.maxDate("2030-12-31")
|
|
173
|
-
.required()
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### `phone()` — Phone number with country code
|
|
177
|
-
|
|
178
|
-
```typescript
|
|
179
|
-
phone({ name: "mobile", label: "Mobile" })
|
|
180
|
-
.defaultCountry("FRA") // ISO 3166-1 alpha-3 country code
|
|
181
|
-
.required()
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### `select()` — Single-choice dropdown
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
select({ name: "status", label: "Status" })
|
|
188
|
-
.options([
|
|
189
|
-
{ label: "Lead", value: "lead", color: "blue" },
|
|
190
|
-
{ label: "Client", value: "client", color: "green" },
|
|
191
|
-
])
|
|
192
|
-
.required()
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
Options shape: `{ label: string, value: string, color?: string, description?: string }`.
|
|
196
|
-
Colors: `"gray"` | `"red"` | `"orange"` | `"yellow"` | `"green"` | `"blue"` | `"purple"`.
|
|
197
|
-
|
|
198
|
-
Use `.option({ ... })` to append a single option instead of replacing all.
|
|
199
|
-
|
|
200
|
-
### `multiselect()` — Multi-choice dropdown
|
|
201
|
-
|
|
202
|
-
Same API as `select()`, but the value is an array of selected option values.
|
|
203
|
-
|
|
204
|
-
```typescript
|
|
205
|
-
multiselect({ name: "tags", label: "Tags" })
|
|
206
|
-
.options([
|
|
207
|
-
{ label: "Urgent", value: "urgent", color: "red" },
|
|
208
|
-
{ label: "Pending", value: "pending", color: "orange" },
|
|
209
|
-
])
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### `status()` — Status with workflow groups
|
|
213
|
-
|
|
214
|
-
Like `select()` but options support a `group` field for visual grouping.
|
|
215
|
-
|
|
216
|
-
```typescript
|
|
217
|
-
status({ name: "stage", label: "Stage" })
|
|
218
|
-
.options([
|
|
219
|
-
{ label: "To Do", value: "todo", color: "gray", group: "idle" },
|
|
220
|
-
{ label: "In Progress", value: "in_progress", color: "blue", group: "in_progress" },
|
|
221
|
-
{ label: "Done", value: "done", color: "green", group: "finished" },
|
|
222
|
-
])
|
|
223
|
-
.required()
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
Option groups: `"idle"` | `"in_progress"` | `"finished"`. The `group` field is optional.
|
|
227
|
-
|
|
228
|
-
### `user()` — User reference
|
|
229
|
-
|
|
230
|
-
```typescript
|
|
231
|
-
user({ name: "owner", label: "Owner" })
|
|
232
|
-
.multiple() // allow multiple actors
|
|
233
|
-
.types(["user", "agent"]) // allow users, agents, or both
|
|
234
|
-
.required()
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### `file()` — File upload
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
file({ name: "attachment", label: "Attachment" })
|
|
241
|
-
.multiple() // allow multiple files
|
|
242
|
-
.maxFiles(5)
|
|
243
|
-
.maxSize(10 * 1024 * 1024) // 10 MB in bytes
|
|
244
|
-
.allowedTypes(["image/png", "image/jpeg", "application/pdf"])
|
|
245
|
-
.required()
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
### `document()` — Structured document with slots and edge properties
|
|
249
|
-
|
|
250
|
-
Documents wrap files with agent-compatible processing and edge-level metadata.
|
|
251
|
-
|
|
252
|
-
```typescript
|
|
253
|
-
document({ name: "contracts", label: "Contracts" })
|
|
254
|
-
.slots([{ name: "file", label: "File", acceptedMimeTypes: ["application/pdf"] }])
|
|
255
|
-
.qualifyWith(
|
|
256
|
-
date({ name: "signedDate", label: "Signed Date" }),
|
|
257
|
-
select({ name: "status", label: "Status" }).options([
|
|
258
|
-
{ label: "Pending", value: "pending", color: "orange" },
|
|
259
|
-
{ label: "Validated", value: "validated", color: "green" },
|
|
260
|
-
])
|
|
261
|
-
)
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
For multi-sided documents (e.g. identity card), define slots:
|
|
265
|
-
|
|
266
|
-
```typescript
|
|
267
|
-
document({ name: "identityDoc", label: "Identity Document" })
|
|
268
|
-
.slots([
|
|
269
|
-
{
|
|
270
|
-
name: "recto",
|
|
271
|
-
label: "Front",
|
|
272
|
-
required: true,
|
|
273
|
-
acceptedMimeTypes: ["image/*", "application/pdf"],
|
|
274
|
-
maxSizeBytes: 10 * 1024 * 1024,
|
|
275
|
-
},
|
|
276
|
-
{
|
|
277
|
-
name: "verso",
|
|
278
|
-
label: "Back",
|
|
279
|
-
required: false,
|
|
280
|
-
acceptedMimeTypes: ["image/*", "application/pdf"],
|
|
281
|
-
maxSizeBytes: 10 * 1024 * 1024,
|
|
282
|
-
},
|
|
283
|
-
])
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
### `location()` — Address / geographic location
|
|
287
|
-
|
|
288
|
-
```typescript
|
|
289
|
-
location({ name: "address", label: "Address" })
|
|
290
|
-
.granularity("full") // "full" | "address" | "city" | "state" | "country"
|
|
291
|
-
.defaultCountry("FRA")
|
|
292
|
-
.allowedCountries(["FRA", "BEL", "CHE"])
|
|
293
|
-
.required()
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
### `relation()` — Record reference (single or many)
|
|
297
|
-
|
|
298
|
-
```typescript
|
|
299
|
-
// Single relation (default cardinality: "one")
|
|
300
|
-
relation({ name: "company", label: "Company" })
|
|
301
|
-
.to("companies")
|
|
302
|
-
.required()
|
|
303
|
-
|
|
304
|
-
// Multi relation
|
|
305
|
-
relation({ name: "contacts", label: "Contacts" })
|
|
306
|
-
.to("contacts")
|
|
307
|
-
.many()
|
|
308
|
-
|
|
309
|
-
// Polymorphic (multiple targets)
|
|
310
|
-
relation({ name: "linked", label: "Linked" })
|
|
311
|
-
.to("companies")
|
|
312
|
-
.to("contacts")
|
|
313
|
-
.many()
|
|
314
|
-
|
|
315
|
-
// Universal (link to any object)
|
|
316
|
-
relation({ name: "reference", label: "Reference" })
|
|
317
|
-
.toAny()
|
|
318
|
-
.many()
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
**Qualified relations** — add metadata on each edge:
|
|
322
|
-
|
|
323
|
-
```typescript
|
|
324
|
-
relation({ name: "shareholders", label: "Shareholders" })
|
|
325
|
-
.to("contacts")
|
|
326
|
-
.many()
|
|
327
|
-
.qualifyWith(
|
|
328
|
-
select({ name: "role", label: "Role" }).options([
|
|
329
|
-
{ label: "Founder", value: "founder", color: "blue" },
|
|
330
|
-
{ label: "Investor", value: "investor", color: "green" },
|
|
331
|
-
]).required(),
|
|
332
|
-
number({ name: "shares", label: "Shares %" }).min(0).max(100),
|
|
333
|
-
)
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
**Bilateral relations** — automatically sync the reverse side:
|
|
337
|
-
|
|
338
|
-
```typescript
|
|
339
|
-
relation({ name: "relationships", label: "Relationships" })
|
|
340
|
-
.to("contacts")
|
|
341
|
-
.many()
|
|
342
|
-
.bilateral({ object: "contacts", attribute: "relationships" })
|
|
343
|
-
.qualifyWith(
|
|
344
|
-
select({ name: "type", label: "Type" }).options([...]).required()
|
|
345
|
-
)
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
Multi-relation constraints:
|
|
349
|
-
|
|
350
|
-
```typescript
|
|
351
|
-
.maxItems(10) // maximum number of linked records
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
### `formula()` — Computed read-only value
|
|
355
|
-
|
|
356
|
-
Formula attributes cannot be required (they are always read-only).
|
|
357
|
-
|
|
358
|
-
```typescript
|
|
359
|
-
formula({ name: "total", label: "Total" })
|
|
360
|
-
.expression("price * quantity")
|
|
361
|
-
.returns("number") // "text" | "number" | "boolean" | "date"
|
|
362
|
-
.decimals(2)
|
|
363
|
-
|
|
364
|
-
formula({ name: "fullName", label: "Full Name" })
|
|
365
|
-
.expression("CONCAT(firstName, ' ', lastName)")
|
|
366
|
-
.returns("text")
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
### `rollup()` — Aggregation from related records
|
|
370
|
-
|
|
371
|
-
Rollup attributes cannot be required (they are always read-only).
|
|
372
|
-
|
|
373
|
-
```typescript
|
|
374
|
-
// Sum a numeric field across related records
|
|
375
|
-
rollup({ name: "totalRevenue", label: "Total Revenue" })
|
|
376
|
-
.from("orders") // name of the relation attribute on THIS object
|
|
377
|
-
.aggregate("amount") // attribute name on the RELATED object
|
|
378
|
-
.using("sum")
|
|
379
|
-
.decimals(2)
|
|
380
|
-
|
|
381
|
-
// Count related records
|
|
382
|
-
rollup({ name: "orderCount", label: "Order Count" })
|
|
383
|
-
.from("orders")
|
|
384
|
-
.aggregate("id")
|
|
385
|
-
.using("count")
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
Available `.using()` functions:
|
|
389
|
-
- Numeric only: `"sum"` | `"avg"`
|
|
390
|
-
- Date only: `"earliest"` | `"latest"`
|
|
391
|
-
- Universal: `"count"` | `"countValues"` | `"countUniqueValues"` | `"countEmpty"` | `"percentEmpty"` | `"percentNotEmpty"` | `"original"`
|
|
392
|
-
|
|
393
|
-
The `"original"` function returns raw values as an array rendered as the target type.
|
|
394
|
-
When using `"original"` on a select/status/multiselect, also call `.targetType()` and `.targetOptions()`:
|
|
395
|
-
|
|
396
|
-
```typescript
|
|
397
|
-
rollup({ name: "dealStages", label: "Deal Stages" })
|
|
398
|
-
.from("deals")
|
|
399
|
-
.aggregate("stage")
|
|
400
|
-
.using("original")
|
|
401
|
-
.targetType("status")
|
|
402
|
-
.targetOptions([
|
|
403
|
-
{ label: "Open", value: "open", color: "blue" },
|
|
404
|
-
{ label: "Won", value: "won", color: "green" },
|
|
405
|
-
{ label: "Lost", value: "lost", color: "red" },
|
|
406
|
-
])
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
---
|
|
410
|
-
|
|
411
|
-
## Common Attribute Options (all types)
|
|
412
|
-
|
|
413
|
-
```typescript
|
|
414
|
-
.required() // value is mandatory
|
|
415
|
-
.optional() // value is optional (default)
|
|
416
|
-
.hidden() // hidden from default UI views
|
|
417
|
-
.description("Tooltip help text") // shown as tooltip in UI
|
|
418
|
-
.icon("star") // IconName from @stndrds/constants
|
|
419
|
-
.placeholder("Enter a value...") // input placeholder text
|
|
420
|
-
.order(5) // display order within the form
|
|
421
|
-
.defaultValue(...) // pre-fill new records
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
---
|
|
425
|
-
|
|
426
|
-
## Promoting a Custom Attribute
|
|
427
|
-
|
|
428
|
-
When a user has created a custom attribute and you want to make it system:
|
|
429
|
-
|
|
430
|
-
**Before (user-created in DB, not in code):**
|
|
431
|
-
```
|
|
432
|
-
drift: deal.urgence (text, label: "Urgence")
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
**After (promoted to system in code):**
|
|
436
|
-
```typescript
|
|
437
|
-
const DEAL = object({ name: "deals", label: "Deal" })
|
|
438
|
-
.labelExpression("{{ name }}")
|
|
439
|
-
.attribute(text({ name: "name", label: "Name" }).required())
|
|
440
|
-
.attribute(text({ name: "urgence", label: "Urgence" })) // ← add this
|
|
441
|
-
.build();
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
On next boot, the existing custom attribute is automatically promoted to system.
|
|
445
|
-
|
|
446
|
-
---
|
|
447
|
-
|
|
448
|
-
## Tolerating a Custom Attribute (sealed objects only)
|
|
449
|
-
|
|
450
|
-
When you seal an object but want to acknowledge an existing custom attribute:
|
|
451
|
-
|
|
452
|
-
```typescript
|
|
453
|
-
const INVOICE = object({ name: "invoice", label: "Invoice" })
|
|
454
|
-
.sealed()
|
|
455
|
-
.tolerate(["legacy_ref"]) // ← known custom attr, won't fail CI
|
|
456
|
-
.labelExpression("{{ ref }}")
|
|
457
|
-
.attribute(text({ name: "ref", label: "Reference" }).required())
|
|
458
|
-
.build();
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
---
|
|
462
|
-
|
|
463
|
-
## Migrations
|
|
464
|
-
|
|
465
|
-
When you rename or change the type of an attribute, declare a migration.
|
|
466
|
-
Versions must start at 2 and be sequential (2, 3, 4, ...).
|
|
467
|
-
|
|
468
|
-
```typescript
|
|
469
|
-
object({ name: "deals", label: "Deal" })
|
|
470
|
-
.migration(2, (m) => m.renameAttribute("old_name", "new_name"))
|
|
471
|
-
.migration(3, (m) => m.changeType("score", "text", "number", { transform: (v) => v }))
|
|
472
|
-
.migration(4, (m) => m.removeAttribute("deprecated_field"))
|
|
473
|
-
.labelExpression("{{ new_name }}")
|
|
474
|
-
.attribute(text({ name: "new_name", label: "Name" }).required())
|
|
475
|
-
.build();
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
Available migration operations:
|
|
479
|
-
- `m.renameAttribute(from, to)` — rename an attribute
|
|
480
|
-
- `m.changeType(name, fromType, toType, options?)` — change attribute type
|
|
481
|
-
- `m.removeAttribute(name)` — permanently remove an attribute
|
|
482
|
-
- `m.addAttribute(attribute)` — add a new attribute via migration
|
|
483
|
-
- `m.updateConfig(name, config)` — patch attribute config (label, options, etc.)
|
|
484
|
-
|
|
485
|
-
---
|
|
486
|
-
|
|
487
|
-
## Type Inference
|
|
488
|
-
|
|
489
|
-
Extract TypeScript types from your schema builders for use in frontend code:
|
|
490
|
-
|
|
491
|
-
```typescript
|
|
492
|
-
// ✅ Correct: infer types from the builder (before .build())
|
|
493
|
-
const dealBuilder = object({ name: "deals", label: "Deal" })
|
|
494
|
-
.labelExpression("{{ name }}")
|
|
495
|
-
.attribute(text({ name: "name", label: "Name" }).required())
|
|
496
|
-
.attribute(number({ name: "amount", label: "Amount" }));
|
|
497
|
-
|
|
498
|
-
type DealRecord = typeof dealBuilder.$infer.record; // full record with id, timestamps
|
|
499
|
-
type DealCreate = typeof dealBuilder.$infer.create; // input for creation
|
|
500
|
-
type DealUpdate = typeof dealBuilder.$infer.update; // input for partial update
|
|
501
|
-
|
|
502
|
-
// Export the compiled definition separately
|
|
503
|
-
export const DEAL = dealBuilder.build();
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
You can also use `ExtractRecord` from `@stndrds/schema`:
|
|
507
|
-
|
|
508
|
-
```typescript
|
|
509
|
-
import { ExtractRecord } from "@stndrds/schema";
|
|
510
|
-
type DealRecord = ExtractRecord<typeof dealBuilder>; // same as $infer.record
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
Note: `ExtractRecord` works on the **builder** (`typeof dealBuilder`), not the compiled definition (`typeof DEAL`).
|