@localess/schema 4.0.3-dev.20261004111503 → 4.0.3-dev.20261004121049

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/SKILL.md CHANGED
@@ -111,16 +111,15 @@ in `schemas`. Both are normalized to string ids at runtime by `defineSchema`,
111
111
  matching the wire format exactly — but the *type* keeps the literal id, so
112
112
  `InferContent` resolves `OPTION` fields to the referenced enum's literal
113
113
  value union instead of plain `string`, and `SCHEMA`/`SCHEMAS` fields to the
114
- allowed schemas' content types (or every `NODE` schema in the config when the
115
- `schemas` list is omitted).
114
+ allowed schemas' content types.
116
115
 
117
116
  Fallbacks when a reference can't be resolved against the config:
118
117
 
119
118
  - `OPTION`/`OPTIONS` whose `source` id is not in the config → `string` (or
120
119
  `string[]`), mirroring the CLI's `type generate` behavior.
121
120
  - `InferEnum` of an enum with no `values` → `string`.
122
- - Unrestricted `SCHEMA`/`SCHEMAS` (no `schemas` list) in a config with no
123
- `NODE` schemas → `{ _id: string; _schema: string }` (or an array of it).
121
+ - `SCHEMA`/`SCHEMAS` with an empty `schemas` list (a `validate()` error) →
122
+ `{ _id: string; _schema: string }` (or an array of it).
124
123
  - `InferContentData` of a config with no `ROOT` schema → `never`.
125
124
 
126
125
  ## Field kinds
@@ -142,7 +141,7 @@ everything else is optional.
142
141
  | `LINK` | — | `ContentLink` |
143
142
  | `REFERENCE` / `REFERENCES` | `path?` | `ContentReference` / `ContentReference[]` |
144
143
  | `ASSET` / `ASSETS` | `fileTypes?: AssetFileType[]`, `fileType?: AssetFileType` | `ContentAsset` / `ContentAsset[]` |
145
- | `SCHEMA` / `SCHEMAS` | `schemas?` (allowed ids or `defineSchema` results; unrestricted when absent) | allowed schemas' content type / array of it |
144
+ | `SCHEMA` / `SCHEMAS` | `schemas` (required: allowed `NODE` ids or `defineSchema` results — the editor offers exactly these) | allowed schemas' content type / array of it |
146
145
 
147
146
  `AssetFileType` is `'ANY' | 'IMAGE' | 'VIDEO' | 'TEXT' | 'AUDIO' | 'APPLICATION'`.
148
147
 
@@ -175,7 +174,9 @@ schema in the config is checked, not just the first. Rules and their `code`:
175
174
  | `field/unresolved-source` | `OPTION`/`OPTIONS` `source` must be the id of a schema in the config. |
176
175
  | `field/source-not-enum` | ...and that schema must be an `ENUM`. |
177
176
  | `field/unresolved-schema-ref` | Every `SCHEMA`/`SCHEMAS` `schemas` entry must be the id of a schema in the config. |
178
- | `field/schema-ref-is-enum` | ...and that schema must be `ROOT` or `NODE`, not `ENUM`. |
177
+ | `field/schema-ref-is-enum` | ...and that schema must be `NODE`, not `ENUM`. |
178
+ | `field/schema-ref-is-root` | ...nor `ROOT` — the Studio editor only offers `NODE` schemas. |
179
+ | `field/missing-schemas` | A `SCHEMA`/`SCHEMAS` field must list at least one schema in `schemas`; with none the editor can add no block. |
179
180
 
180
181
  `validate` does not check for stray properties from the wrong field kind —
181
182
  use `defineField` for that (above). Duplicate field names and duplicate
package/dist/define.d.ts CHANGED
@@ -27,7 +27,7 @@ type FieldInputOf<F> = F extends {
27
27
  } : F extends {
28
28
  kind: 'SCHEMA' | 'SCHEMAS';
29
29
  } ? Omit<DeepReadonly<F>, 'schemas'> & {
30
- readonly schemas?: readonly (string | ComponentDefinition)[];
30
+ readonly schemas: readonly (string | ComponentDefinition)[];
31
31
  } : DeepReadonly<F>;
32
32
  /**
33
33
  * A field as authored: OPTION/OPTIONS source and SCHEMA/SCHEMAS schemas accept by-value refs.
@@ -141,8 +141,8 @@ export declare function defineEnum<const TId extends string, const TValues exten
141
141
  * - `OPTIONS` — `source` (required, same as `OPTION`), `minValues?`, `maxValues?`
142
142
  * - `REFERENCE` / `REFERENCES` — `path?`
143
143
  * - `ASSET` / `ASSETS` — `fileTypes?`, `fileType?`
144
- * - `SCHEMA` / `SCHEMAS` — `schemas?` (allowed definitions from `defineSchema`, or their ids;
145
- * every `NODE` schema in the config is allowed when omitted)
144
+ * - `SCHEMA` / `SCHEMAS` — `schemas` (required: the allowed `NODE` definitions from `defineSchema`,
145
+ * or their ids — the Studio editor offers exactly these, so an empty list leaves the field unusable)
146
146
  *
147
147
  * Full field-kind reference, including the type each kind infers to: `docs/schema.md`.
148
148
  *
package/dist/index.js CHANGED
@@ -44,8 +44,8 @@ function defineEnum(definition) {
44
44
  * - `OPTIONS` — `source` (required, same as `OPTION`), `minValues?`, `maxValues?`
45
45
  * - `REFERENCE` / `REFERENCES` — `path?`
46
46
  * - `ASSET` / `ASSETS` — `fileTypes?`, `fileType?`
47
- * - `SCHEMA` / `SCHEMAS` — `schemas?` (allowed definitions from `defineSchema`, or their ids;
48
- * every `NODE` schema in the config is allowed when omitted)
47
+ * - `SCHEMA` / `SCHEMAS` — `schemas` (required: the allowed `NODE` definitions from `defineSchema`,
48
+ * or their ids — the Studio editor offers exactly these, so an empty list leaves the field unusable)
49
49
  *
50
50
  * Full field-kind reference, including the type each kind infers to: `docs/schema.md`.
51
51
  *
@@ -193,10 +193,14 @@ function validate(config) {
193
193
  if (!source) error("field/unresolved-source", fieldPath, `source '${field.source}' does not resolve to a schema in the config`);
194
194
  else if (source.type !== "ENUM") error("field/source-not-enum", fieldPath, `source '${field.source}' must be an ENUM schema`);
195
195
  }
196
- if (field.kind === "SCHEMA" || field.kind === "SCHEMAS") for (const ref of field.schemas ?? []) {
197
- const target = byId.get(ref);
198
- if (!target) error("field/unresolved-schema-ref", fieldPath, `schemas ref '${ref}' does not resolve to a schema in the config`);
199
- else if (target.type === "ENUM") error("field/schema-ref-is-enum", fieldPath, `schemas ref '${ref}' must be a ROOT or NODE schema, not an ENUM`);
196
+ if (field.kind === "SCHEMA" || field.kind === "SCHEMAS") {
197
+ if (!field.schemas || field.schemas.length === 0) error("field/missing-schemas", fieldPath, `${field.kind} field must list at least one NODE schema in 'schemas'`);
198
+ for (const ref of field.schemas ?? []) {
199
+ const target = byId.get(ref);
200
+ if (!target) error("field/unresolved-schema-ref", fieldPath, `schemas ref '${ref}' does not resolve to a schema in the config`);
201
+ else if (target.type === "ENUM") error("field/schema-ref-is-enum", fieldPath, `schemas ref '${ref}' must be a NODE schema, not an ENUM`);
202
+ else if (target.type === "ROOT") error("field/schema-ref-is-root", fieldPath, `schemas ref '${ref}' must be a NODE schema; the editor never offers a ROOT schema`);
203
+ }
200
204
  }
201
205
  }
202
206
  }
package/dist/index.mjs CHANGED
@@ -43,8 +43,8 @@ function defineEnum(definition) {
43
43
  * - `OPTIONS` — `source` (required, same as `OPTION`), `minValues?`, `maxValues?`
44
44
  * - `REFERENCE` / `REFERENCES` — `path?`
45
45
  * - `ASSET` / `ASSETS` — `fileTypes?`, `fileType?`
46
- * - `SCHEMA` / `SCHEMAS` — `schemas?` (allowed definitions from `defineSchema`, or their ids;
47
- * every `NODE` schema in the config is allowed when omitted)
46
+ * - `SCHEMA` / `SCHEMAS` — `schemas` (required: the allowed `NODE` definitions from `defineSchema`,
47
+ * or their ids — the Studio editor offers exactly these, so an empty list leaves the field unusable)
48
48
  *
49
49
  * Full field-kind reference, including the type each kind infers to: `docs/schema.md`.
50
50
  *
@@ -192,10 +192,14 @@ function validate(config) {
192
192
  if (!source) error("field/unresolved-source", fieldPath, `source '${field.source}' does not resolve to a schema in the config`);
193
193
  else if (source.type !== "ENUM") error("field/source-not-enum", fieldPath, `source '${field.source}' must be an ENUM schema`);
194
194
  }
195
- if (field.kind === "SCHEMA" || field.kind === "SCHEMAS") for (const ref of field.schemas ?? []) {
196
- const target = byId.get(ref);
197
- if (!target) error("field/unresolved-schema-ref", fieldPath, `schemas ref '${ref}' does not resolve to a schema in the config`);
198
- else if (target.type === "ENUM") error("field/schema-ref-is-enum", fieldPath, `schemas ref '${ref}' must be a ROOT or NODE schema, not an ENUM`);
195
+ if (field.kind === "SCHEMA" || field.kind === "SCHEMAS") {
196
+ if (!field.schemas || field.schemas.length === 0) error("field/missing-schemas", fieldPath, `${field.kind} field must list at least one NODE schema in 'schemas'`);
197
+ for (const ref of field.schemas ?? []) {
198
+ const target = byId.get(ref);
199
+ if (!target) error("field/unresolved-schema-ref", fieldPath, `schemas ref '${ref}' does not resolve to a schema in the config`);
200
+ else if (target.type === "ENUM") error("field/schema-ref-is-enum", fieldPath, `schemas ref '${ref}' must be a NODE schema, not an ENUM`);
201
+ else if (target.type === "ROOT") error("field/schema-ref-is-root", fieldPath, `schemas ref '${ref}' must be a NODE schema; the editor never offers a ROOT schema`);
202
+ }
199
203
  }
200
204
  }
201
205
  }
package/dist/infer.d.ts CHANGED
@@ -9,13 +9,6 @@ type SchemasOf<C> = C extends {
9
9
  type FindById<C, Id> = Extract<SchemasOf<C>, {
10
10
  id: Id;
11
11
  }>;
12
- type NodeIds<C> = [Extract<SchemasOf<C>, {
13
- type: 'NODE';
14
- }>] extends [never] ? never : Extract<SchemasOf<C>, {
15
- type: 'NODE';
16
- }> extends {
17
- id: infer Id extends string;
18
- } ? Id : never;
19
12
  type EnumValuesUnion<E> = E extends {
20
13
  values: readonly SchemaEnumValue[];
21
14
  } ? E extends {
@@ -26,13 +19,13 @@ type EnumValuesUnion<E> = E extends {
26
19
  /** Literal union of an ENUM definition's values (falls back to `string` when values are absent). */
27
20
  export type InferEnum<E> = EnumValuesUnion<E>;
28
21
  type ResolveEnum<Id extends string, C> = [FindById<C, Id>] extends [never] ? string : EnumValuesUnion<FindById<C, Id>>;
29
- type AllowedIds<F, C> = F extends {
22
+ type AllowedIds<F> = F extends {
30
23
  schemas: readonly (infer Id extends string)[];
31
- } ? Id : NodeIds<C>;
32
- type ResolveSchemaContent<F, C> = [AllowedIds<F, C>] extends [never] ? {
24
+ } ? Id : never;
25
+ type ResolveSchemaContent<F, C> = [AllowedIds<F>] extends [never] ? {
33
26
  _id: string;
34
27
  _schema: string;
35
- } : ContentByIds<AllowedIds<F, C>, C>;
28
+ } : ContentByIds<AllowedIds<F>, C>;
36
29
  type ContentByIds<Id extends string, C> = Id extends unknown ? InferContent<FindById<C, Id>, C> : never;
37
30
  type FieldValue<F, C> = F extends {
38
31
  kind: 'TEXT' | 'TEXTAREA' | 'MARKDOWN' | 'COLOR' | 'DATE' | 'DATETIME';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@localess/schema",
3
- "version": "4.0.3-dev.20261004111503",
3
+ "version": "4.0.3-dev.20261004121049",
4
4
  "description": "Programmatic schema definitions for Localess with TypeScript content type inference.",
5
5
  "keywords": [
6
6
  "localess",
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "license": "MIT",
45
45
  "dependencies": {
46
- "@localess/model": "4.0.3-dev.20261004111503"
46
+ "@localess/model": "4.0.3-dev.20261004121049"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^24",