@jxsuite/schema 1.2.0 → 1.3.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/LICENSE +21 -0
- package/class-schema.json +103 -1
- package/defs/children-value.schema.ts +10 -1
- package/defs/class-def.schema.ts +107 -1
- package/defs/element-def.schema.ts +15 -2
- package/defs/external-class-def.schema.ts +14 -1
- package/defs/function-def.schema.ts +8 -0
- package/defs/index.ts +4 -0
- package/defs/project-config.schema.ts +3 -2
- package/defs/style-object.schema.ts +4 -10
- package/package.json +1 -1
- package/project-schema.json +20 -2
- package/schema.json +1052 -19
- package/schemas/project.core.schema.json +20 -2
- package/src/guards.ts +10 -0
- package/src/schema.ts +41 -3
- package/src/validate-project.ts +13 -0
|
@@ -105,6 +105,24 @@
|
|
|
105
105
|
},
|
|
106
106
|
"required": ["$ref"],
|
|
107
107
|
"type": "object"
|
|
108
|
+
},
|
|
109
|
+
"StyleObject": {
|
|
110
|
+
"additionalProperties": {
|
|
111
|
+
"oneOf": [
|
|
112
|
+
{
|
|
113
|
+
"type": "string"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"type": "number"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"$ref": "#/$defs/StyleObject"
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
"description": "CSS style definition. camelCase property names follow CSSOM convention. Keys starting with :, ., &, or [ are treated as nested CSS selectors. Keys matching $media breakpoint names (or other @-prefixed at-rules) group nested rules. Nesting is recursive: selector and at-rule groups may nest to arbitrary depth (e.g. breakpoint → selector → pseudo-class), mirroring the compiler's recursive emission.",
|
|
124
|
+
"properties": {},
|
|
125
|
+
"type": "object"
|
|
108
126
|
}
|
|
109
127
|
},
|
|
110
128
|
"$id": "https://jxsuite.com/schema/project/core/v2",
|
|
@@ -366,11 +384,11 @@
|
|
|
366
384
|
"type": "number"
|
|
367
385
|
},
|
|
368
386
|
{
|
|
369
|
-
"
|
|
387
|
+
"$ref": "#/$defs/StyleObject"
|
|
370
388
|
}
|
|
371
389
|
]
|
|
372
390
|
},
|
|
373
|
-
"description": "Global CSS styles applied to the <body> element. Uses the same camelCase CSSOM convention as component styles.",
|
|
391
|
+
"description": "Global CSS styles applied to the <body> element. Uses the same camelCase CSSOM convention and recursive selector/at-rule nesting as component styles.",
|
|
374
392
|
"type": "object"
|
|
375
393
|
},
|
|
376
394
|
"url": {
|
package/src/guards.ts
CHANGED
|
@@ -174,6 +174,16 @@ export function isTemplateString(value?: unknown): value is string {
|
|
|
174
174
|
return typeof value === "string" && value.includes("${");
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Whether a function `body` returns a value — the heuristic that classifies a `$prototype:
|
|
179
|
+
* "Function"` as a computed value vs an event handler. Uses a word boundary so identifiers like
|
|
180
|
+
* `returned` do not match. Shared by the runtime interpreter and the compiler so both agree on the
|
|
181
|
+
* classification.
|
|
182
|
+
*/
|
|
183
|
+
export function bodyReturnsValue(body: string): boolean {
|
|
184
|
+
return /\breturn\b/.test(body);
|
|
185
|
+
}
|
|
186
|
+
|
|
177
187
|
// ─── Style guards & accessors ───────────────────────────────────────────────────
|
|
178
188
|
|
|
179
189
|
/** A nested style object (selector/media block), as opposed to a scalar CSS value. */
|
package/src/schema.ts
CHANGED
|
@@ -92,7 +92,10 @@ import {
|
|
|
92
92
|
classFieldDefSchema,
|
|
93
93
|
classMethodDefSchema,
|
|
94
94
|
classParameterDefSchema,
|
|
95
|
+
connectorBlockDefSchema,
|
|
95
96
|
formatDefSchema,
|
|
97
|
+
projectBlockDefSchema,
|
|
98
|
+
serverBlockDefSchema,
|
|
96
99
|
studioHintsSchema,
|
|
97
100
|
} from "../defs/class-def.schema";
|
|
98
101
|
import { extensionManifestSchema } from "../defs/extension-manifest.schema";
|
|
@@ -182,7 +185,11 @@ function buildEventHandlerProperties(eventHandlers: string[]) {
|
|
|
182
185
|
for (const name of eventHandlers) {
|
|
183
186
|
properties[name] = {
|
|
184
187
|
description: `Event handler for the "${name.slice(2)}" event.`,
|
|
185
|
-
oneOf: [
|
|
188
|
+
oneOf: [
|
|
189
|
+
{ $ref: "#/$defs/RefObject" },
|
|
190
|
+
{ $ref: "#/$defs/ExpressionEntry" },
|
|
191
|
+
{ $ref: "#/$defs/FunctionDef" },
|
|
192
|
+
],
|
|
186
193
|
};
|
|
187
194
|
}
|
|
188
195
|
return properties;
|
|
@@ -216,6 +223,9 @@ export async function generateSchema() {
|
|
|
216
223
|
ClassConstructorDef: classConstructorDefSchema,
|
|
217
224
|
ClassMethodDef: classMethodDefSchema,
|
|
218
225
|
FormatDef: formatDefSchema,
|
|
226
|
+
ProjectBlockDef: projectBlockDefSchema,
|
|
227
|
+
ServerBlockDef: serverBlockDefSchema,
|
|
228
|
+
ConnectorBlockDef: connectorBlockDefSchema,
|
|
219
229
|
StudioHints: studioHintsSchema,
|
|
220
230
|
ExternalClassDef: externalClassDefSchema,
|
|
221
231
|
ExpressionPointer: expressionPointerSchema,
|
|
@@ -415,6 +425,7 @@ export function generateProjectSchema() {
|
|
|
415
425
|
return {
|
|
416
426
|
$defs: {
|
|
417
427
|
ImageConfig: imageConfigSchema,
|
|
428
|
+
StyleObject: styleObjectSchema,
|
|
418
429
|
},
|
|
419
430
|
$id: "https://jxsuite.com/schema/project/v1",
|
|
420
431
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -446,6 +457,7 @@ export function generateProjectCoreSchema() {
|
|
|
446
457
|
ImageConfig: imageConfigSchema,
|
|
447
458
|
JxFieldSchema: jxFieldSchemaDef,
|
|
448
459
|
RelationshipRef: relationshipRefSchema,
|
|
460
|
+
StyleObject: styleObjectSchema,
|
|
449
461
|
},
|
|
450
462
|
$id: "https://jxsuite.com/schema/project/core/v2",
|
|
451
463
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -514,7 +526,10 @@ export function generateClassSchema() {
|
|
|
514
526
|
ClassFieldDef: classFieldDefSchema,
|
|
515
527
|
ClassMethodDef: classMethodDefSchema,
|
|
516
528
|
ClassParameterDef: classParameterDefSchema,
|
|
529
|
+
ConnectorBlockDef: connectorBlockDefSchema,
|
|
517
530
|
FormatDef: formatDefSchema,
|
|
531
|
+
ProjectBlockDef: projectBlockDefSchema,
|
|
532
|
+
ServerBlockDef: serverBlockDefSchema,
|
|
518
533
|
StudioHints: studioHintsSchema,
|
|
519
534
|
},
|
|
520
535
|
$id: "https://jxsuite.com/schema/class/v1",
|
|
@@ -565,6 +580,7 @@ export function generateClassSchema() {
|
|
|
565
580
|
},
|
|
566
581
|
$schema: { type: "string" },
|
|
567
582
|
$studio: { $ref: "#/$defs/StudioHints" },
|
|
583
|
+
connector: { $ref: "#/$defs/ConnectorBlockDef" },
|
|
568
584
|
description: { type: "string" },
|
|
569
585
|
extends: {
|
|
570
586
|
description: "Base class — string name or $ref to another .class.json.",
|
|
@@ -579,6 +595,8 @@ export function generateClassSchema() {
|
|
|
579
595
|
],
|
|
580
596
|
},
|
|
581
597
|
format: { $ref: "#/$defs/FormatDef" },
|
|
598
|
+
project: { $ref: "#/$defs/ProjectBlockDef" },
|
|
599
|
+
server: { $ref: "#/$defs/ServerBlockDef" },
|
|
582
600
|
title: {
|
|
583
601
|
description: "PascalCase class name, used as the export name.",
|
|
584
602
|
examples: ["MarkdownFile", "DataSource", "Calculator"],
|
|
@@ -613,7 +631,19 @@ type AjvCtor = new (opts: {
|
|
|
613
631
|
}) => AjvInstance;
|
|
614
632
|
type AddFormatsFn = (ajv: AjvInstance) => void;
|
|
615
633
|
|
|
616
|
-
|
|
634
|
+
/**
|
|
635
|
+
* Validate a document against an arbitrary JSON Schema 2020-12 document (e.g. a bundled per-project
|
|
636
|
+
* document.schema.json from `jx schema`). The schema must be self-contained — external refs are not
|
|
637
|
+
* resolved here.
|
|
638
|
+
*
|
|
639
|
+
* @param {Record<string, unknown>} doc - The document to validate
|
|
640
|
+
* @param {Record<string, unknown>} schema - A self-contained 2020-12 schema
|
|
641
|
+
* @returns {Promise<{ errors: unknown[] | null; valid: boolean }>}
|
|
642
|
+
*/
|
|
643
|
+
export async function validateWithSchema(
|
|
644
|
+
doc: Record<string, unknown>,
|
|
645
|
+
schema: Record<string, unknown>,
|
|
646
|
+
) {
|
|
617
647
|
let Ajv: AjvCtor, addFormats: AddFormatsFn;
|
|
618
648
|
try {
|
|
619
649
|
// The generated schema is JSON Schema 2020-12, so use the matching Ajv build
|
|
@@ -628,13 +658,21 @@ export async function validateDocument(doc: Record<string, unknown>) {
|
|
|
628
658
|
const ajv = new Ajv({ allErrors: true, ownProperties: true, strict: false });
|
|
629
659
|
addFormats(ajv);
|
|
630
660
|
|
|
631
|
-
const schema = await generateSchema();
|
|
632
661
|
const validate = ajv.compile(schema);
|
|
633
662
|
const valid = validate(doc);
|
|
634
663
|
|
|
635
664
|
return { errors: validate.errors ?? null, valid };
|
|
636
665
|
}
|
|
637
666
|
|
|
667
|
+
export async function validateDocument(doc: Record<string, unknown>) {
|
|
668
|
+
return validateWithSchema(doc, (await generateSchema()) as Record<string, unknown>);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/** Validate a .class.json class definition against the generated class schema. */
|
|
672
|
+
export async function validateClass(doc: Record<string, unknown>) {
|
|
673
|
+
return validateWithSchema(doc, generateClassSchema() as Record<string, unknown>);
|
|
674
|
+
}
|
|
675
|
+
|
|
638
676
|
// ─── CLI ──────────────────────────────────────────────────────────────────────
|
|
639
677
|
|
|
640
678
|
async function runSchemaCli() {
|
package/src/validate-project.ts
CHANGED
|
@@ -133,6 +133,19 @@ async function loadProjectSchemaRef(uri: string, root: string): Promise<Record<s
|
|
|
133
133
|
const selfRequire = createRequire(import.meta.url);
|
|
134
134
|
resolved = selfRequire.resolve(specifier);
|
|
135
135
|
} catch (error) {
|
|
136
|
+
// Exports-map subpath resolution is brittle against version-skewed shadowing installs (a
|
|
137
|
+
// Stale local copy terminates Node's walk-up with ERR_PACKAGE_PATH_NOT_EXPORTED). Our own
|
|
138
|
+
// Schema files are pure data and the running validator's copy is guaranteed to match, so
|
|
139
|
+
// Read them straight off this package's disk as the last resort.
|
|
140
|
+
const ownPrefix = "@jxsuite/schema/";
|
|
141
|
+
if (specifier.startsWith(ownPrefix)) {
|
|
142
|
+
const ownFile = fileURLToPath(
|
|
143
|
+
new URL(`../${specifier.slice(ownPrefix.length)}`, import.meta.url),
|
|
144
|
+
);
|
|
145
|
+
if (existsSync(ownFile)) {
|
|
146
|
+
return JSON.parse(readFileSync(ownFile, "utf8")) as Record<string, unknown>;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
136
149
|
throw new Error(
|
|
137
150
|
`Schema ref "${uri}" does not exist and "${specifier}" is not resolvable from the ` +
|
|
138
151
|
`project or the host`,
|