@jxsuite/schema 0.35.0 → 1.0.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/defs/extension-manifest.schema.ts +47 -0
- package/defs/field-schema.schema.ts +58 -0
- package/defs/index.ts +5 -2
- package/defs/project-config.schema.ts +18 -10
- package/extension-manifest.schema.json +54 -0
- package/package.json +12 -4
- package/project-schema.json +17 -53
- package/schema.json +0 -44
- package/schemas/document.paths.schema.json +6 -0
- package/schemas/project.core.schema.json +441 -0
- package/schemas/project.fields.schema.json +14 -0
- package/src/extension-registry.ts +270 -0
- package/src/format-registry.ts +83 -13
- package/src/project-schemas.ts +262 -0
- package/src/schema.ts +107 -7
- package/src/validate-project.ts +144 -0
- package/types.ts +41 -8
- package/defs/content-type-def.schema.ts +0 -37
package/src/schema.ts
CHANGED
|
@@ -90,7 +90,13 @@ import {
|
|
|
90
90
|
formatDefSchema,
|
|
91
91
|
studioHintsSchema,
|
|
92
92
|
} from "../defs/class-def.schema";
|
|
93
|
-
import {
|
|
93
|
+
import { extensionManifestSchema } from "../defs/extension-manifest.schema";
|
|
94
|
+
import {
|
|
95
|
+
DOCUMENT_PATHS_SCHEMA_ID,
|
|
96
|
+
PROJECT_FIELDS_SCHEMA_ID,
|
|
97
|
+
jxFieldSchemaDef,
|
|
98
|
+
relationshipRefSchema,
|
|
99
|
+
} from "../defs/field-schema.schema";
|
|
94
100
|
import { imageConfigSchema } from "../defs/image-config.schema";
|
|
95
101
|
import { projectConfigSchema } from "../defs/project-config.schema";
|
|
96
102
|
|
|
@@ -261,7 +267,6 @@ export async function generateSchema() {
|
|
|
261
267
|
JsonSchemaType: jsonSchemaTypeSchema,
|
|
262
268
|
HeadEntry: headEntrySchema,
|
|
263
269
|
ImageConfig: imageConfigSchema,
|
|
264
|
-
ContentTypeDef: contentTypeDefSchema,
|
|
265
270
|
},
|
|
266
271
|
$id: "https://jxsuite.com/schema/v1",
|
|
267
272
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -395,23 +400,97 @@ export async function generateSchema() {
|
|
|
395
400
|
export function generateProjectSchema() {
|
|
396
401
|
return {
|
|
397
402
|
$defs: {
|
|
398
|
-
ContentTypeDef: contentTypeDefSchema,
|
|
399
403
|
ImageConfig: imageConfigSchema,
|
|
400
404
|
},
|
|
401
405
|
$id: "https://jxsuite.com/schema/project/v1",
|
|
402
406
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
403
|
-
additionalProperties: false,
|
|
404
407
|
description:
|
|
405
408
|
"Schema for Jx project.json files. " +
|
|
406
409
|
"A project.json file is the root anchor file for a Jx project, " +
|
|
407
|
-
"declaring site metadata, default settings, global styles,
|
|
408
|
-
"and build configuration."
|
|
410
|
+
"declaring site metadata, default settings, global styles, extensions, " +
|
|
411
|
+
"and build configuration. Open by design: extension-contributed sections " +
|
|
412
|
+
"are opaque top-level keys; the generated per-project entry document " +
|
|
413
|
+
"(project.schema.json) closes the composition.",
|
|
409
414
|
properties: projectConfigSchema.properties,
|
|
410
415
|
title: "Jx Project",
|
|
411
416
|
type: "object",
|
|
412
417
|
};
|
|
413
418
|
}
|
|
414
419
|
|
|
420
|
+
// ─── Project Core Fragment Generator ─────────────────────────────────────────
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* The core fragment of the composed per-project schema (specs/extensions.md §5.1): core
|
|
424
|
+
* project.json properties only — no extension sections — and deliberately open, because closure
|
|
425
|
+
* (`unevaluatedProperties: false`) happens in the generated entry document. Publishes the
|
|
426
|
+
* `JxFieldSchema` and `RelationshipRef` defs the entry document unions into the `jxFieldSchema`
|
|
427
|
+
* dynamic anchor.
|
|
428
|
+
*/
|
|
429
|
+
export function generateProjectCoreSchema() {
|
|
430
|
+
return {
|
|
431
|
+
$defs: {
|
|
432
|
+
ImageConfig: imageConfigSchema,
|
|
433
|
+
JxFieldSchema: jxFieldSchemaDef,
|
|
434
|
+
RelationshipRef: relationshipRefSchema,
|
|
435
|
+
},
|
|
436
|
+
$id: "https://jxsuite.com/schema/project/core/v2",
|
|
437
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
438
|
+
description:
|
|
439
|
+
"Core fragment of the Jx project.json schema. Open by design: extension fragments " +
|
|
440
|
+
"contribute their own top-level sections and the generated per-project entry document " +
|
|
441
|
+
"(project.schema.json) closes the composition. See specs/extensions.md §5.",
|
|
442
|
+
properties: projectConfigSchema.properties,
|
|
443
|
+
title: "Jx Project Core",
|
|
444
|
+
type: "object",
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ─── Union Resource Generators ───────────────────────────────────────────────
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* The shipped DEFAULT field-union resource. The generated per-project entry document re-embeds a
|
|
452
|
+
* resource under the same $id with the effective union (adding extension extras); by standard
|
|
453
|
+
* compound-document $id resolution the embed shadows this default wherever both are in play.
|
|
454
|
+
*/
|
|
455
|
+
export function generateProjectFieldsSchema() {
|
|
456
|
+
return {
|
|
457
|
+
$id: PROJECT_FIELDS_SCHEMA_ID,
|
|
458
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
459
|
+
anyOf: [
|
|
460
|
+
{ $ref: "https://jxsuite.com/schema/project/core/v2#/$defs/JxFieldSchema" },
|
|
461
|
+
{ $ref: "https://jxsuite.com/schema/project/core/v2#/$defs/RelationshipRef" },
|
|
462
|
+
],
|
|
463
|
+
description:
|
|
464
|
+
"Default field-schema union for section entry schemas: the core field shape plus " +
|
|
465
|
+
"relationship references. Per-project entry documents override this resource with the " +
|
|
466
|
+
"effective union. See specs/extensions.md §5.3.",
|
|
467
|
+
title: "Jx Project Field Union",
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** The shipped DEFAULT $paths-value resource: permissive until extensions contribute shapes. */
|
|
472
|
+
export function generateDocumentPathsSchema() {
|
|
473
|
+
return {
|
|
474
|
+
$id: DOCUMENT_PATHS_SCHEMA_ID,
|
|
475
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
476
|
+
description:
|
|
477
|
+
"Default $paths-value union for documents. Per-project entry documents override this " +
|
|
478
|
+
"resource with the union of extension-contributed paths shapes.",
|
|
479
|
+
title: "Jx Document Paths Union",
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ─── Extension Manifest Schema Generator ─────────────────────────────────────
|
|
484
|
+
|
|
485
|
+
export function generateExtensionManifestSchema() {
|
|
486
|
+
return {
|
|
487
|
+
$id: "https://jxsuite.com/schema/extension-manifest/v1",
|
|
488
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
489
|
+
title: "Jx Extension Manifest",
|
|
490
|
+
...extensionManifestSchema,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
415
494
|
// ─── Class Schema Generator ─────────────────────────────────────────────────
|
|
416
495
|
|
|
417
496
|
export function generateClassSchema() {
|
|
@@ -545,7 +624,7 @@ export async function validateDocument(doc: Record<string, unknown>) {
|
|
|
545
624
|
// ─── CLI ──────────────────────────────────────────────────────────────────────
|
|
546
625
|
|
|
547
626
|
async function runSchemaCli() {
|
|
548
|
-
const { writeFileSync } = await import("node:fs");
|
|
627
|
+
const { mkdirSync, writeFileSync } = await import("node:fs");
|
|
549
628
|
const { resolve, dirname } = await import("node:path");
|
|
550
629
|
|
|
551
630
|
const schemaDir = dirname(resolve(process.argv[1] as string, ".."));
|
|
@@ -553,10 +632,18 @@ async function runSchemaCli() {
|
|
|
553
632
|
const componentSchema = await generateSchema();
|
|
554
633
|
const projectSchema = generateProjectSchema();
|
|
555
634
|
const classSchema = generateClassSchema();
|
|
635
|
+
const projectCoreSchema = generateProjectCoreSchema();
|
|
636
|
+
const manifestSchema = generateExtensionManifestSchema();
|
|
637
|
+
const fieldsSchema = generateProjectFieldsSchema();
|
|
638
|
+
const pathsSchema = generateDocumentPathsSchema();
|
|
556
639
|
|
|
557
640
|
const componentStr = JSON.stringify(componentSchema, null, 2);
|
|
558
641
|
const projectStr = JSON.stringify(projectSchema, null, 2);
|
|
559
642
|
const classStr = JSON.stringify(classSchema, null, 2);
|
|
643
|
+
const projectCoreStr = JSON.stringify(projectCoreSchema, null, 2);
|
|
644
|
+
const manifestStr = JSON.stringify(manifestSchema, null, 2);
|
|
645
|
+
const fieldsStr = JSON.stringify(fieldsSchema, null, 2);
|
|
646
|
+
const pathsStr = JSON.stringify(pathsSchema, null, 2);
|
|
560
647
|
|
|
561
648
|
const [out] = process.argv.slice(2);
|
|
562
649
|
|
|
@@ -567,10 +654,23 @@ async function runSchemaCli() {
|
|
|
567
654
|
writeFileSync(resolve(schemaDir, "schema.json"), componentStr, "utf8");
|
|
568
655
|
writeFileSync(resolve(schemaDir, "project-schema.json"), projectStr, "utf8");
|
|
569
656
|
writeFileSync(resolve(schemaDir, "class-schema.json"), classStr, "utf8");
|
|
657
|
+
mkdirSync(resolve(schemaDir, "schemas"), { recursive: true });
|
|
658
|
+
writeFileSync(
|
|
659
|
+
resolve(schemaDir, "schemas", "project.core.schema.json"),
|
|
660
|
+
projectCoreStr,
|
|
661
|
+
"utf8",
|
|
662
|
+
);
|
|
663
|
+
writeFileSync(resolve(schemaDir, "schemas", "project.fields.schema.json"), fieldsStr, "utf8");
|
|
664
|
+
writeFileSync(resolve(schemaDir, "schemas", "document.paths.schema.json"), pathsStr, "utf8");
|
|
665
|
+
writeFileSync(resolve(schemaDir, "extension-manifest.schema.json"), manifestStr, "utf8");
|
|
570
666
|
console.error("Generated:");
|
|
571
667
|
console.error(" schema.json (component)");
|
|
572
668
|
console.error(" project-schema.json");
|
|
573
669
|
console.error(" class-schema.json");
|
|
670
|
+
console.error(" schemas/project.core.schema.json");
|
|
671
|
+
console.error(" schemas/project.fields.schema.json");
|
|
672
|
+
console.error(" schemas/document.paths.schema.json");
|
|
673
|
+
console.error(" extension-manifest.schema.json");
|
|
574
674
|
}
|
|
575
675
|
}
|
|
576
676
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate-project — project.json validation against the generated per-project entry schema
|
|
3
|
+
* (specs/extensions.md §5.4).
|
|
4
|
+
*
|
|
5
|
+
* Node-only by design (mirrors the dynamic-ajv-import pattern of `validateDocument`): reads
|
|
6
|
+
* `<root>/project.json` and `<root>/project.schema.json`, compiles the entry document with
|
|
7
|
+
* ajv-2020, and resolves its relative `$ref`s through a file loader restricted to the project root
|
|
8
|
+
* and its node_modules. The entry file's URL is injected as `$id` at load time so the committed,
|
|
9
|
+
* machine-path-free relative refs resolve.
|
|
10
|
+
*
|
|
11
|
+
* When a `./node_modules/...` ref does not exist on disk (workspace hoisting leaves in-repo
|
|
12
|
+
* starters without their own node_modules), the loader falls back to host resolution of the bare
|
|
13
|
+
* specifier — the same project-first-then-host order as createNodeFormatIO.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
17
|
+
import { createRequire } from "node:module";
|
|
18
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
19
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
20
|
+
|
|
21
|
+
export interface ProjectValidationResult {
|
|
22
|
+
valid: boolean;
|
|
23
|
+
errors: unknown[] | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Minimal structural types for the optional `ajv` peer dependency (no direct dependency, and it
|
|
27
|
+
// Ships no types usable from here) — same pattern as validateDocument in src/schema.ts.
|
|
28
|
+
interface AjvValidateFn {
|
|
29
|
+
(doc: unknown): boolean;
|
|
30
|
+
errors?: unknown[] | null;
|
|
31
|
+
}
|
|
32
|
+
interface AjvInstance {
|
|
33
|
+
compileAsync: (schema: unknown) => Promise<AjvValidateFn>;
|
|
34
|
+
}
|
|
35
|
+
type AjvCtor = new (opts: {
|
|
36
|
+
allErrors: boolean;
|
|
37
|
+
strict: boolean;
|
|
38
|
+
loadSchema: (uri: string) => Promise<Record<string, unknown>>;
|
|
39
|
+
}) => AjvInstance;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Validate `<projectRoot>/project.json` against `<projectRoot>/project.schema.json`.
|
|
43
|
+
*
|
|
44
|
+
* Validation failures are data (`{ valid: false, errors }`); infrastructure failures — missing
|
|
45
|
+
* files, unresolvable refs, refs escaping the project root — throw.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} projectRoot - Absolute path to the project directory
|
|
48
|
+
* @returns {Promise<ProjectValidationResult>}
|
|
49
|
+
*/
|
|
50
|
+
export async function validateProjectFile(projectRoot: string): Promise<ProjectValidationResult> {
|
|
51
|
+
const root = resolve(projectRoot);
|
|
52
|
+
const projectPath = resolve(root, "project.json");
|
|
53
|
+
const schemaPath = resolve(root, "project.schema.json");
|
|
54
|
+
|
|
55
|
+
if (!existsSync(projectPath)) {
|
|
56
|
+
throw new Error(`project.json not found in ${root}`);
|
|
57
|
+
}
|
|
58
|
+
if (!existsSync(schemaPath)) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`project.schema.json not found in ${root} — run \`jx schema\` to generate it from ` +
|
|
61
|
+
`project.json#/extensions`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const project = JSON.parse(readFileSync(projectPath, "utf8")) as Record<string, unknown>;
|
|
66
|
+
const entrySchema = JSON.parse(readFileSync(schemaPath, "utf8")) as Record<string, unknown>;
|
|
67
|
+
// The committed entry document is machine-path-free; injecting its file URL as $id makes its
|
|
68
|
+
// Relative ./node_modules/... refs resolve against the project root.
|
|
69
|
+
entrySchema.$id = pathToFileURL(schemaPath).href;
|
|
70
|
+
|
|
71
|
+
let Ajv: AjvCtor;
|
|
72
|
+
try {
|
|
73
|
+
// The entry document is JSON Schema 2020-12, so use the matching Ajv build.
|
|
74
|
+
({ default: Ajv } = (await import("ajv/dist/2020")) as { default: AjvCtor });
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new Error("Project validation requires ajv: bun add ajv", { cause: error });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const ajv = new Ajv({
|
|
80
|
+
allErrors: true,
|
|
81
|
+
loadSchema: (uri) => loadProjectSchemaRef(uri, root),
|
|
82
|
+
strict: false,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const validate = await ajv.compileAsync(entrySchema);
|
|
86
|
+
const valid = validate(project);
|
|
87
|
+
return { errors: validate.errors ?? null, valid };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Restricted schema loader: only `file:` URLs inside the project root are served. Missing files
|
|
92
|
+
* under `<root>/node_modules/` fall back to host resolution of the corresponding bare specifier
|
|
93
|
+
* (in-repo starters have no local node_modules; the fragments still ship with the host).
|
|
94
|
+
*
|
|
95
|
+
* @param {string} uri - The `$ref` target ajv could not resolve from already-loaded resources
|
|
96
|
+
* @param {string} root - Absolute project root
|
|
97
|
+
* @returns {Promise<Record<string, unknown>>}
|
|
98
|
+
*/
|
|
99
|
+
async function loadProjectSchemaRef(uri: string, root: string): Promise<Record<string, unknown>> {
|
|
100
|
+
if (!uri.startsWith("file:")) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Cannot resolve schema ref "${uri}": only project-relative file refs are loadable ` +
|
|
103
|
+
`(canonical URIs must resolve from already-loaded fragments)`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const filePath = fileURLToPath(uri);
|
|
108
|
+
const rel = relative(root, filePath);
|
|
109
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
110
|
+
throw new Error(`Schema ref "${uri}" escapes the project root ${root}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (existsSync(filePath)) {
|
|
114
|
+
return JSON.parse(readFileSync(filePath, "utf8")) as Record<string, unknown>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Host fallback: <root>/node_modules/<specifier> is absent — resolve the specifier through the
|
|
118
|
+
// Project's own resolution first, then the host's (workspace symlinks resolve transparently).
|
|
119
|
+
const posixRel = rel.split("\\").join("/");
|
|
120
|
+
const marker = "node_modules/";
|
|
121
|
+
const markerIndex = posixRel.lastIndexOf(marker);
|
|
122
|
+
if (markerIndex === -1) {
|
|
123
|
+
throw new Error(`Schema ref "${uri}" does not exist`);
|
|
124
|
+
}
|
|
125
|
+
const specifier = posixRel.slice(markerIndex + marker.length);
|
|
126
|
+
|
|
127
|
+
let resolved: string;
|
|
128
|
+
try {
|
|
129
|
+
const projRequire = createRequire(resolve(root, "package.json"));
|
|
130
|
+
resolved = projRequire.resolve(specifier);
|
|
131
|
+
} catch {
|
|
132
|
+
try {
|
|
133
|
+
const selfRequire = createRequire(import.meta.url);
|
|
134
|
+
resolved = selfRequire.resolve(specifier);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Schema ref "${uri}" does not exist and "${specifier}" is not resolvable from the ` +
|
|
138
|
+
`project or the host`,
|
|
139
|
+
{ cause: error },
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return JSON.parse(readFileSync(resolved, "utf8")) as Record<string, unknown>;
|
|
144
|
+
}
|
package/types.ts
CHANGED
|
@@ -3,7 +3,6 @@ import type { FromSchema } from "json-schema-to-ts";
|
|
|
3
3
|
import type { headEntrySchema } from "./defs/head-entry.schema";
|
|
4
4
|
import type { imageConfigSchema } from "./defs/image-config.schema";
|
|
5
5
|
import type { cemEventSchema, cemParameterSchema } from "./defs/cem.schema";
|
|
6
|
-
import type { contentTypeDefSchema } from "./defs/content-type-def.schema";
|
|
7
6
|
import type { projectConfigSchema } from "./defs/project-config.schema";
|
|
8
7
|
import type { refObjectSchema } from "./defs/ref-object.schema";
|
|
9
8
|
import type { styleObjectSchema } from "./defs/style-object.schema";
|
|
@@ -32,7 +31,6 @@ export namespace Strict {
|
|
|
32
31
|
export type ImageConfig = FromSchema<typeof imageConfigSchema>;
|
|
33
32
|
export type CemParameter = FromSchema<typeof cemParameterSchema>;
|
|
34
33
|
export type CemEvent = FromSchema<typeof cemEventSchema>;
|
|
35
|
-
export type ContentTypeDef = FromSchema<typeof contentTypeDefSchema>;
|
|
36
34
|
export type RefObject = FromSchema<typeof refObjectSchema>;
|
|
37
35
|
export type Style = FromSchema<typeof styleObjectSchema>;
|
|
38
36
|
export type Element = FromSchema<typeof elementDefSchema>;
|
|
@@ -186,8 +184,6 @@ export interface CemEvent {
|
|
|
186
184
|
deprecated?: boolean | string;
|
|
187
185
|
}
|
|
188
186
|
|
|
189
|
-
export type ContentTypeDef = FromSchema<typeof contentTypeDefSchema>;
|
|
190
|
-
|
|
191
187
|
export type RefObject = FromSchema<typeof refObjectSchema>;
|
|
192
188
|
|
|
193
189
|
export interface JxStyle {
|
|
@@ -445,6 +441,8 @@ export interface DeployConfig {
|
|
|
445
441
|
}
|
|
446
442
|
|
|
447
443
|
export interface ProjectConfig {
|
|
444
|
+
/** Relative path to the generated per-project schema (written by `jx schema`). */
|
|
445
|
+
$schema?: string;
|
|
448
446
|
name?: string;
|
|
449
447
|
url?: string;
|
|
450
448
|
state?: Record<string, unknown>;
|
|
@@ -460,7 +458,12 @@ export interface ProjectConfig {
|
|
|
460
458
|
};
|
|
461
459
|
images?: ImageConfig;
|
|
462
460
|
imports?: Record<string, string>;
|
|
463
|
-
|
|
461
|
+
/**
|
|
462
|
+
* Extension packages: bare package names (resolved project-first) or relative paths. Each must
|
|
463
|
+
* export jx-extension.json. Extension-contributed sections (e.g. `content`) are opaque top-level
|
|
464
|
+
* keys absorbed by the index signature.
|
|
465
|
+
*/
|
|
466
|
+
extensions?: string[];
|
|
464
467
|
/** Redirect map: source path → destination (or destination with HTTP status). */
|
|
465
468
|
redirects?: Record<string, string | { destination: string; status?: number }>;
|
|
466
469
|
defaults?: {
|
|
@@ -480,13 +483,15 @@ export interface ProjectConfig {
|
|
|
480
483
|
export type JxAttributeValue = Bindable<string | number | boolean>;
|
|
481
484
|
|
|
482
485
|
/**
|
|
483
|
-
* A dynamic-route path source (spec §4.3):
|
|
484
|
-
*
|
|
486
|
+
* A dynamic-route path source (spec §4.3): explicit values, data-file ref, a legacy array of param
|
|
487
|
+
* objects, or an extension-discriminated source (an object carrying a `resolvePaths` discriminator
|
|
488
|
+
* key registered by an enabled extension, e.g. the parser's `contentType` — the extension owns the
|
|
489
|
+
* narrow shape).
|
|
485
490
|
*/
|
|
486
491
|
export type JxPathsDef =
|
|
487
|
-
| { contentType: string; param?: string; field?: string }
|
|
488
492
|
| { values: JsonValue[]; param?: string }
|
|
489
493
|
| { $ref: string; param?: string; field?: string }
|
|
494
|
+
| { param?: string; field?: string; [discriminator: string]: unknown }
|
|
490
495
|
| Record<string, JsonValue>[];
|
|
491
496
|
|
|
492
497
|
/**
|
|
@@ -541,6 +546,8 @@ export interface JxMutableNode {
|
|
|
541
546
|
export type JxPath = (string | number)[];
|
|
542
547
|
|
|
543
548
|
// ─── Content Type Schema ────────────────────────────────────────────────────────
|
|
549
|
+
// Part-3 cleanup: move to parser — the parser extension owns the content-section shapes now; these
|
|
550
|
+
// Stay here only while the studio still imports them from @jxsuite/schema/types.
|
|
544
551
|
|
|
545
552
|
/** One field within a content-type JSON schema; recursive for nested objects. */
|
|
546
553
|
export interface ContentTypeSchemaField {
|
|
@@ -558,3 +565,29 @@ export interface ContentTypeSchema {
|
|
|
558
565
|
required?: string[];
|
|
559
566
|
[key: string]: unknown;
|
|
560
567
|
}
|
|
568
|
+
|
|
569
|
+
// ─── Content Wire Types ─────────────────────────────────────────────────────────
|
|
570
|
+
// Core wire shapes for content loading (specs/extensions.md §8): format classes' `load`
|
|
571
|
+
// Capabilities return ContentLoaderEntry[], and hosts pass the entries through untouched.
|
|
572
|
+
// Extensions implement them; core only threads them.
|
|
573
|
+
|
|
574
|
+
/** A table-of-contents entry extracted from a loaded content document. */
|
|
575
|
+
export interface TocEntry {
|
|
576
|
+
depth: number;
|
|
577
|
+
text: string;
|
|
578
|
+
id: string;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/** One loaded content entry, as produced by a format class's `load` capability. */
|
|
582
|
+
export interface ContentLoaderEntry {
|
|
583
|
+
id: string;
|
|
584
|
+
data: Record<string, unknown>;
|
|
585
|
+
body: string | null;
|
|
586
|
+
$children?: (JxElement | string)[];
|
|
587
|
+
_meta?: {
|
|
588
|
+
excerpt?: string;
|
|
589
|
+
toc?: TocEntry[];
|
|
590
|
+
readingTime?: number;
|
|
591
|
+
wordCount?: number;
|
|
592
|
+
};
|
|
593
|
+
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
export const contentTypeDefSchema = {
|
|
2
|
-
description:
|
|
3
|
-
"Content type definition. Defines the source directory, frontmatter schema, and element dependencies.",
|
|
4
|
-
properties: {
|
|
5
|
-
$elements: {
|
|
6
|
-
description: "Custom elements available in markdown directives for this content type.",
|
|
7
|
-
items: {
|
|
8
|
-
oneOf: [
|
|
9
|
-
{
|
|
10
|
-
properties: { $ref: { type: "string" } },
|
|
11
|
-
required: ["$ref"],
|
|
12
|
-
type: "object",
|
|
13
|
-
},
|
|
14
|
-
{ type: "string" },
|
|
15
|
-
],
|
|
16
|
-
},
|
|
17
|
-
type: "array",
|
|
18
|
-
},
|
|
19
|
-
format: {
|
|
20
|
-
description:
|
|
21
|
-
"Import name of a format class declared in the project imports map " +
|
|
22
|
-
'(e.g. "Markdown", "Csv"). When omitted, the format is derived from the ' +
|
|
23
|
-
'source file extension via the format registry. "json" is the only built-in.',
|
|
24
|
-
type: "string",
|
|
25
|
-
},
|
|
26
|
-
schema: {
|
|
27
|
-
description: "JSON Schema for validating frontmatter of content type entries.",
|
|
28
|
-
type: "object",
|
|
29
|
-
},
|
|
30
|
-
source: {
|
|
31
|
-
description: "Path to the content directory relative to the project root.",
|
|
32
|
-
examples: ["./content/blog/", "./content/docs/"],
|
|
33
|
-
type: "string",
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
type: "object",
|
|
37
|
-
} as const;
|