Skip to content

Commit 09df571

Browse files
docs(skills): type-check 14 more objectstack-data examples and fix the Field import path (#31618)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} --> _Requested by **Steedos** · [project thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9Ubb2wtMdjxrW3hze65M5q3BXN)_ Refs #31112 (slice 2 of a per-skill split: item 4, objectstack-data; the card stays open) Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:objectstack-data 技能包里又有 14 段 TypeScript 示例接入了示例编译检查(95 段中由 11 段变为 25 段),并修正一处会让照抄者编译失败的示例:`Field` 原写成从 `@objectstack/spec` 根入口导入,根入口并不导出它,改为 `@objectstack/spec/data`。 - 为什么改:AI 编写元数据时最先照抄这些示例;没接入检查的示例在规范改名后会悄悄失效,这次就查出一处已经失效的导入。 - 风险与回滚:只改发布技能包的文档文字,不动任何运行时代码;每个文件的 token 上限都未抬高,新增字节靠同文件删减重复句支付。回滚即 revert 本 PR。 - 先看:`skills/objectstack-data/rules/field-types.md`(唯一的示例修正)、`skills/objectstack-data/rules/indexing.md`(8 段新接入、改动最多)、`skills/objectstack-data/rules/validation.md`(删去两句重复说明以支付额度)。 - 两分钟验证:`pnpm --filter @objectstack/spec check:skill-examples`,预期最后一行为 322 段示例全部通过类型检查。 - 需要你做的:作为受管面审批人审阅并批准本 PR(skills/** 属 Tier H)。 ## What changes `skills/objectstack-data` had 11 of its 95 fenced `ts`/`typescript` blocks under `check:skill-examples` (measured on `main` 158cc2a, all `*.md` outside `evals/`, `_index.md` excluded). It now has 25. Every new mark is the os:check marker on the line above the fence; wrappers are an import plus a typed `const`, never more than three lines. | File | marked before → after / blocks | tokens before → after / ceiling | lines before → after | |:--|:--|:--|:--| | `rules/indexing.md` | 0 → 8 / 10 | 2284 → 2495 / 3241 | 236 → 260 | | `references/data-hooks.md` | 1 → 5 / 24 | 9905 → 9987 / 10011 | 942 → 953 | | `rules/lifecycle.md` | 0 → 1 / 2 | 1588 → 1590 / 1590 | 164 → 165 | | `rules/field-types.md` | 0 → 1 / 8 | 3199 → 3199 / 3199 | 252 → 253 | | `rules/validation.md` | 2 → 2 / 15 | 2761 → 2754 / 2761 | 329 → 331 | | `references/examples-objects.md` | 4 → 4 / 5 | 1710 → 1723 / 1728 | 189 → 191 | | untouched: `SKILL.md`, `seeds.md`, `datasources.md`, `relationships.md`, `security.md` | 2/4, 0/4, 0/1, 0/13, 2/9 | headroom 9, 0, 3, 0, 0 | unchanged | Skills bundle reading: no `SKILL.md` changed, so the sum of every published `SKILL.md` stays 4592 lines. - **`rules/indexing.md`**: eight index examples become `const indexes: ObjectIndex[] = [ … ];` (or `const index: ObjectIndex`) with `import type { ObjectIndex } from '@objectstack/spec/data'`. A retired `type` / `partial` key or an unscoped `unique: true` now fails the gate. The two blocks holding ❌ keys stay unmarked; the opening "Guide for …" line, which carried no rule, now says so. - **`references/data-hooks.md`**: the full `defineHook` definition is marked as written; patterns 1 to 3 gain their `defineHook` import. Pattern 4 stays unmarked (its `record: any` parameter is a bare `any`, which the gate refuses), as do the context shapes and fragments; the intro says so once. - **`rules/lifecycle.md`**: the `ObjectSchema.create` syntax block is marked as written, paid by "Guide for declaring" becoming "Declaring". The ❌ block sits under the heading "Validation Rules (rejected at parse time)". - **`rules/field-types.md`**: the `Field.*` factory block is marked as `const fields = { … };`. - **`references/examples-objects.md`**: the `protection` type excerpt gets one line saying it is an excerpt and unchecked. ### Example fixes - `rules/field-types.md:19` (was line 18): `import { Field } from '@objectstack/spec'` does not compile. The spec root exports no `Field`: namespace exports were removed from the root for tree-shaking (`packages/spec/src/index.ts`, "NAMESPACE EXPORTS — REMOVED"), and every example app imports it from `@objectstack/spec/data`. Now `import { Field } from '@objectstack/spec/data'`. The prose at line 12 named the same wrong path, "(`@objectstack/spec`)"; that parenthetical is removed, because the import line right below it now carries the path and the gate checks it. - `rules/validation.md:325`: the ✅ "Correct — Validation Scoped to Events" example had no `name`, which every validation rule requires (`BaseValidationRuleShape`). It gains `name: 'no_draft_publish'`. The block stays unmarked because it is an object-literal fragment. ### Trims that paid the marks (each cut rule and where it survives) - `rules/validation.md`, Uniqueness section: "There is **no `unique` validation type**." This survives at "Available Rule Types": "**No `unique` type** — enforce uniqueness with a **unique index**". - `rules/validation.md`, Cross-Field section: "Same inverted semantics as `script` — the predicate is the **failure** condition." This survives at "Expression Syntax" ("For `script` **and** `cross_field`, the predicate expresses the **failure** condition") and in the block's own `// ❌ TRUE = invalid` comment. - `rules/field-types.md:11`: "overwhelmingly" became "mostly", and the wrong import parenthetical went (see above). - `rules/indexing.md:3` and `rules/lifecycle.md:3`: intro wording that carried no rule. ## Tests <details><summary>check:skill-examples, before and after</summary> - Before (`main` 158cc2a): `✅ 308 prose examples type-check across 3 surface(s)`. - After (head c4e232f, the same skills tree as the merged head): `pnpm --filter @objectstack/spec check:skill-examples` exited 0 with `✅ 322 prose examples type-check across 3 surface(s) — every marked block parsed, so tsc ran the SEMANTIC pass on all of them` (skills + docs surface 274 → 288). - Reverse check, run after the commit: planted a retired index `type` (`rules/indexing.md:142`), an unknown hook key `runAsUser` (`references/data-hooks.md:747`) and the old root `Field` import (`rules/field-types.md:19`). The checker exited 1 with TS2353, TS2353 and TS2305 at exactly those lines. Restored with `git checkout HEAD -- skills/objectstack-data`; `git status --porcelain` was empty after. </details> ## Acceptance notes - `check:skill-examples` is not in the derived gate list for a `skills/**` diff, so it was run by hand. - Blocks left unmarked because the file has no headroom to pay for a wrapper: `rules/relationships.md` (headroom 0; six `ObjectSchema.create` blocks, which each need an import, and three of them hold two `export default`s), `rules/security.md` (0; the two `rowLevelSecurity` lists), `references/seeds.md` (0; each block names an object defined elsewhere, so it needs a `declare const` line), `rules/datasources.md` (3; the federated-object block needs an import of about 18 tokens), `SKILL.md` (9; the `searchableFields` object and the `indexes` list; the same `indexes` list is checked in `rules/indexing.md`), and the `validations: [ … ]` lists in `rules/validation.md` (about 27 tokens each). The probe that sorted the 95 blocks wrapped each of these with its real type, and every one compiled except the deliberate ❌ examples. So no other example is wrong today; they are just not gated. - The fragment note is unpaid in those five untouched files, which have 0 to 9 tokens of headroom and no sentence that could go without losing a fact. This is reported as a deviation. - No ceiling was raised or lowered, and the ratchet printed no instruction to lower one. ## Verification <details><summary>Derived gates (dispatch-gates --commands, 45 families) on head c4e232f</summary> 44 exited 0. `pnpm check:dual-build-cjs-loads` exited 3 (PREREQUISITE NOT MET: it needs every package's `dist`; CI runs it). `dispatch-gates --ran` exited 0: 45 derived, 44 run, 1 NOT-MEASURED. After merging `main` (head 1d424c0, which touches no `packages/spec` or `skills/` path), the token ratchet, `check:cross-package-test-inputs` (whose script `main` changed), `check:skill-links` and `check-changeset-no-major` were re-run, all exit 0. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01QsR2jRhbPVtqq8gkr4md2P)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent fc4c307 commit 09df571

7 files changed

Lines changed: 77 additions & 26 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): more of objectstack-data's self-contained examples are type-checked by `check:skill-examples`, and the `Field.*` factory example imports from the subpath that exports it
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Fourteen more fenced TypeScript blocks in `skills/objectstack-data` carry the opt-in check marker, so 25 of its 95 are now compiled against the spec: 8 of 10 in `rules/indexing.md` (index lists typed as `ObjectIndex[]` from `@objectstack/spec/data`), 4 more in `references/data-hooks.md` (the full `defineHook` definition and three pattern hooks, which gain their `defineHook` import), and one each in `rules/lifecycle.md` and `rules/field-types.md`. One example was wrong: `rules/field-types.md` imported `Field` from `@objectstack/spec`, which does not export it (the spec root has no namespace exports); it now imports from `@objectstack/spec/data`, and the prose no longer names the root. The ✅ script example in `rules/validation.md` gains the `name` every validation rule requires. The rest stay unmarked: fragments, shapes, ❌ examples, and the blocks whose file had no token headroom left to pay for a wrapper. No rule's teaching changes.

‎skills/objectstack-data/references/data-hooks.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Reference companion to `objectstack-data/SKILL.md`, and the catalog's anchor for
1010
data hooks. Covers the lifecycle events, the sandboxed `body` (and the deprecated
1111
inline `handler`), the `HookContext` API, registration, and the canonical patterns
1212
(delete guards, audit trails, cross-object writes, read masking).
13+
`check:skill-examples` skips the unmarked blocks: fragments, shapes, and pattern 4's bare `any`.
1314

1415

1516
---
@@ -79,6 +80,7 @@ annotation can't catch — a non-`snake_case` `name`, a misspelled key routed
7980
through a spread — fail while you author instead of at deploy, and the export
8081
carries defaults already materialized.
8182

83+
<!-- os:check -->
8284
```typescript
8385
import { P } from '@objectstack/spec';
8486
import { defineHook } from '@objectstack/spec/data';
@@ -736,7 +738,10 @@ additionally require the `api.read` / `api.write` capabilities.
736738

737739
### 1. Preventing Deletion (sandboxed `body`)
738740

741+
<!-- os:check -->
739742
```typescript
743+
import { defineHook } from '@objectstack/spec/data';
744+
740745
const protectStrategicAccounts = defineHook({
741746
name: 'crm_protect_strategic_accounts',
742747
object: 'account',
@@ -760,7 +765,10 @@ const protectStrategicAccounts = defineHook({
760765

761766
### 2. Creating Related Records (audit trail, sandboxed `body`)
762767

768+
<!-- os:check -->
763769
```typescript
770+
import { defineHook } from '@objectstack/spec/data';
771+
764772
const createAuditTrail = defineHook({
765773
name: 'crm_audit_trail',
766774
object: ['account', 'contact', 'opportunity'],
@@ -784,7 +792,10 @@ const createAuditTrail = defineHook({
784792

785793
### 3. Multi-Object Logic (sandboxed `body`)
786794

795+
<!-- os:check -->
787796
```typescript
797+
import { defineHook } from '@objectstack/spec/data';
798+
788799
const cascadeAccountUpdate = defineHook({
789800
name: 'crm_cascade_account_updates',
790801
object: 'account',

‎skills/objectstack-data/references/examples-objects.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ and the lock banner reads.
145145

146146
### Schema
147147

148+
A type excerpt, so `check:skill-examples` skips it:
149+
148150
```ts
149151
protection?: {
150152
/** Lock level — controls what Studio can do to this item. */

‎skills/objectstack-data/rules/field-types.md‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,21 @@
88

99
## Two spellings: `Field.*` factory or object literal
1010

11-
The tables below give the **object-literal** spelling. Real apps overwhelmingly
12-
use the equivalent **`Field.*` factory** (`@objectstack/spec`) — same schema, plus
11+
The tables below give the **object-literal** spelling. Real apps mostly
12+
use the equivalent **`Field.*` factory** — same schema, plus
1313
per-helper JSDoc and inference (`Field.lookup`'s const generic is what lets
1414
`defineSeed()` type its reference values). A helper exists for every type below
1515
except a few authored as literals (`tags`, `progress`, `repeater`, `vector`).
1616

17+
<!-- os:check -->
1718
```typescript
18-
import { Field } from '@objectstack/spec';
19+
import { Field } from '@objectstack/spec/data';
1920

20-
fields: {
21+
const fields = {
2122
name: Field.text({ required: true, maxLength: 255 }),
2223
stage: Field.select(['new', 'won'], { required: true }), // options 1st
2324
account_id: Field.lookup('account', { required: true }), // reference 1st
24-
}
25+
};
2526
```
2627

2728
> **Config columns list only real `FieldSchema` keys.** Per-type display knobs

‎skills/objectstack-data/rules/indexing.md‎

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Index Strategy
22

3-
Guide for creating efficient database indexes in ObjectStack.
3+
The two blocks holding ❌ keys are left unmarked: `check:skill-examples` would refuse them.
44

55
## Default Behavior
66

@@ -34,13 +34,16 @@ and it is the whole surface *because* it is all the driver materializes:
3434
3535
## Syntax
3636

37+
<!-- os:check -->
3738
```typescript
38-
indexes: [
39+
import type { ObjectIndex } from '@objectstack/spec/data';
40+
41+
const indexes: ObjectIndex[] = [
3942
{ fields: ['status', 'created_at'] }, // plain composite
4043
{ fields: ['email'], unique: 'organization' }, // unique per org
4144
{ fields: ['hostname'], unique: 'global' }, // unique platform-wide
4245
{ name: 'idx_acct_status', fields: ['status'] }, // custom name
43-
]
46+
];
4447
```
4548

4649
## Unique scope — always state it (ADR-0120)
@@ -83,22 +86,28 @@ Notes an author has to know:
8386

8487
### Composite Index (Multi-Column)
8588

89+
<!-- os:check -->
8690
```typescript
87-
indexes: [
91+
import type { ObjectIndex } from '@objectstack/spec/data';
92+
93+
const indexes: ObjectIndex[] = [
8894
// Most specific first (status), then sort key
8995
{ fields: ['status', 'created_at'] },
9096

9197
// Can satisfy queries like:
9298
// - WHERE status = 'active'
9399
// - WHERE status = 'active' ORDER BY created_at DESC
94100
// - WHERE status = 'active' AND created_at > '2026-01-01'
95-
]
101+
];
96102
```
97103

98104
### Unique Index
99105

106+
<!-- os:check -->
100107
```typescript
101-
indexes: [
108+
import type { ObjectIndex } from '@objectstack/spec/data';
109+
110+
const indexes: ObjectIndex[] = [
102111
// Single column, one holder per organization
103112
{ fields: ['email'], unique: 'organization' },
104113

@@ -108,7 +117,7 @@ indexes: [
108117

109118
// Single column, one holder across the whole installation
110119
{ fields: ['hostname'], unique: 'global' },
111-
]
120+
];
112121
```
113122

114123
## Incorrect vs Correct
@@ -125,12 +134,15 @@ indexes: [
125134

126135
### ✅ Correct — Declare Only What the Driver Materializes
127136

137+
<!-- os:check -->
128138
```typescript
129-
indexes: [
139+
import type { ObjectIndex } from '@objectstack/spec/data';
140+
141+
const indexes: ObjectIndex[] = [
130142
{ fields: ['status'] }, // ✅ unique: false is the default
131143
{ fields: ['email'], unique: 'organization' }, // ✅ the scope is required
132144
{ fields: ['description', 'notes'] }, // ✅ plain index; see below for full-text
133-
]
145+
];
134146
```
135147

136148
### Composite index order
@@ -184,11 +196,14 @@ uniqueness.
184196

185197
ObjectStack auto-generates index names. To specify custom names:
186198

199+
<!-- os:check -->
187200
```typescript
188-
{
201+
import type { ObjectIndex } from '@objectstack/spec/data';
202+
203+
const index: ObjectIndex = {
189204
name: 'idx_account_status_created', // Custom name
190205
fields: ['status', 'created_at'],
191-
}
206+
};
192207
```
193208

194209
**Auto-generated pattern:** `idx_{object}_{field1}_{field2}_{...}`
@@ -197,11 +212,14 @@ ObjectStack auto-generates index names. To specify custom names:
197212

198213
### Filter by Status + Sort by Date
199214

215+
<!-- os:check -->
200216
```typescript
217+
import type { ObjectIndex } from '@objectstack/spec/data';
218+
201219
// Query: WHERE status = 'active' ORDER BY created_at DESC LIMIT 50
202-
indexes: [
220+
const indexes: ObjectIndex[] = [
203221
{ fields: ['status', 'created_at'] },
204-
]
222+
];
205223
```
206224

207225
### Multi-Tenant Queries
@@ -212,11 +230,14 @@ multi-tenant host the platform adds its single-column index. A non-unique
212230
composite may lead with it; a unique one says `unique: 'organization'` instead
213231
(above):
214232

233+
<!-- os:check -->
215234
```typescript
235+
import type { ObjectIndex } from '@objectstack/spec/data';
236+
216237
// Query: WHERE organization_id = X AND ...
217-
indexes: [
238+
const indexes: ObjectIndex[] = [
218239
{ fields: ['organization_id', 'status', 'created_at'] },
219-
]
240+
];
220241
```
221242

222243
### Text Search / Containment / Geospatial
@@ -226,11 +247,14 @@ is **not** declarable — see "Access methods and partial indexes" above. Declar
226247
the plain index if the column is also filtered or sorted normally, and create
227248
the specialised one from a database-layer migration on the dialect you run.
228249

250+
<!-- os:check -->
229251
```typescript
252+
import type { ObjectIndex } from '@objectstack/spec/data';
253+
230254
// Declaration: plain, portable, and all the driver can build
231-
indexes: [
255+
const indexes: ObjectIndex[] = [
232256
{ fields: ['description'] }, // text search: add a fulltext/GIN index in a migration
233257
{ fields: ['tags'] }, // containment (tags @> [...]): GIN, in a migration
234258
{ fields: ['location'] }, // ST_DWithin(...): GIST, in a migration
235-
]
259+
];
236260
```

‎skills/objectstack-data/rules/lifecycle.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Data Lifecycle & Retention
22

3-
Guide for declaring how long an object's data lives and how its space is
3+
Declaring how long an object's data lives and how its space is
44
reclaimed (ADR-0057). Distinct from
55
**lifecycle hooks** (`beforeInsert` / `afterUpdate` …), which run logic on
66
writes: this page covers the rows' retention, rotation and archival.
@@ -28,6 +28,7 @@ expired rows under a system context, and reclaims driver space.
2828

2929
## Syntax
3030

31+
<!-- os:check -->
3132
```typescript
3233
import { ObjectSchema, Field } from '@objectstack/spec/data';
3334

‎skills/objectstack-data/rules/validation.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
- [External / Custom Validation → Lifecycle Hooks](#external--custom-validation--lifecycle-hooks) · [Validation Properties](#validation-properties)
77
- [Incorrect vs Correct](#incorrect-vs-correct)
88

9+
Unmarked blocks are fragments; `check:skill-examples` skips them.
10+
911
## Available Rule Types
1012

1113
The **complete** set of `type` discriminators accepted by `ValidationRuleSchema`:
@@ -85,7 +87,7 @@ condition: P`record.start_date != null && record.end_date != null && record.end_
8587
8688
## Uniqueness — Use Unique Indexes
8789

88-
There is **no `unique` validation type**. Uniqueness — including composite
90+
Uniqueness — including composite
8991
uniqueness — is declared as a unique **index** on the object:
9092

9193
```typescript
@@ -149,8 +151,7 @@ validations: [
149151

150152
## Cross-Field Validation
151153

152-
Same inverted semantics as `script` — the predicate is the **failure**
153-
condition. `fields` lists the fields involved (used for error targeting).
154+
`fields` lists the fields involved (used for error targeting).
154155

155156
```typescript
156157
validations: [
@@ -321,6 +322,7 @@ priority: 1000 // User validations (run last)
321322

322323
```typescript
323324
{
325+
name: 'no_draft_publish',
324326
type: 'script',
325327
condition: P`record.status == 'draft'`,
326328
message: 'Cannot publish draft records',

0 commit comments

Comments
 (0)