@jxsuite/schema 1.9.0 → 2.1.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/README.md +6 -6
- package/class-schema.json +2 -1
- package/defs/class-def.schema.ts +2 -1
- package/defs/function-def.schema.ts +7 -0
- package/package.json +5 -5
- package/schema.json +37 -1
- package/src/asset-paths.ts +86 -0
- package/src/format-registry.ts +9 -1
- package/src/locale.ts +24 -0
- package/types.ts +14 -0
package/README.md
CHANGED
|
@@ -44,12 +44,12 @@ const valid = await validateDocument(doc); // validate a Jx document
|
|
|
44
44
|
|
|
45
45
|
## Component schema coverage
|
|
46
46
|
|
|
47
|
-
- **`tagName
|
|
48
|
-
- **Element properties
|
|
49
|
-
- **`style
|
|
50
|
-
- **Event handlers
|
|
51
|
-
- **`state` shapes
|
|
52
|
-
- **Built-in `$prototype` values
|
|
47
|
+
- **`tagName`**: all standard HTML element names from `@webref/elements`
|
|
48
|
+
- **Element properties**: all DOM IDL properties from `@webref/idl`
|
|
49
|
+
- **`style`**: all CSSOM camelCase properties from `@webref/css`
|
|
50
|
+
- **Event handlers**: all `EventHandler` names (`onclick`, `oninput`, …)
|
|
51
|
+
- **`state` shapes**: naked value, typed value, computed, function, external class
|
|
52
|
+
- **Built-in `$prototype` values**: `Request`, `LocalStorage`, `SessionStorage`, `Cookie`, `IndexedDB`, `Array`, `Set`, `Map`, `Blob`, `ReadableStream`, `URLSearchParams`, `FormData`
|
|
53
53
|
|
|
54
54
|
## VSCode / editor integration
|
|
55
55
|
|
package/class-schema.json
CHANGED
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"type": "object"
|
|
106
106
|
},
|
|
107
107
|
"ClassMethodDef": {
|
|
108
|
-
"description": "A class method or accessor definition. Capability roles (parse, serialize, discover, load) mark static methods that hosts (compiler, server, studio) invoke for format dispatch.",
|
|
108
|
+
"description": "A class method or accessor definition. Capability roles (parse, serialize, rewrite, discover, load) mark static methods that hosts (compiler, server, studio) invoke for format dispatch.",
|
|
109
109
|
"properties": {
|
|
110
110
|
"$prototype": {
|
|
111
111
|
"const": "Function",
|
|
@@ -175,6 +175,7 @@
|
|
|
175
175
|
"accessor",
|
|
176
176
|
"parse",
|
|
177
177
|
"serialize",
|
|
178
|
+
"rewrite",
|
|
178
179
|
"discover",
|
|
179
180
|
"load",
|
|
180
181
|
"projectData",
|
package/defs/class-def.schema.ts
CHANGED
|
@@ -9,6 +9,7 @@ export const CLASS_METHOD_ROLES = [
|
|
|
9
9
|
"accessor",
|
|
10
10
|
"parse",
|
|
11
11
|
"serialize",
|
|
12
|
+
"rewrite",
|
|
12
13
|
"discover",
|
|
13
14
|
"load",
|
|
14
15
|
"projectData",
|
|
@@ -96,7 +97,7 @@ export const classConstructorDefSchema = {
|
|
|
96
97
|
|
|
97
98
|
export const classMethodDefSchema = {
|
|
98
99
|
description:
|
|
99
|
-
"A class method or accessor definition. Capability roles (parse, serialize, discover, load) " +
|
|
100
|
+
"A class method or accessor definition. Capability roles (parse, serialize, rewrite, discover, load) " +
|
|
100
101
|
"mark static methods that hosts (compiler, server, studio) invoke for format dispatch.",
|
|
101
102
|
properties: {
|
|
102
103
|
$prototype: { const: "Function", type: "string" },
|
|
@@ -9,6 +9,13 @@ export const functionDefSchema = {
|
|
|
9
9
|
description: "Named export in $src module. Defaults to the state key name.",
|
|
10
10
|
type: "string",
|
|
11
11
|
},
|
|
12
|
+
$lazy: {
|
|
13
|
+
description:
|
|
14
|
+
"Load $src on first call instead of importing it up front. The function then returns a " +
|
|
15
|
+
"promise, so it may only be called from a handler or lifecycle hook — never bound as a " +
|
|
16
|
+
"computed value. Use it for code most visitors never reach.",
|
|
17
|
+
type: "boolean",
|
|
18
|
+
},
|
|
12
19
|
$prototype: { const: "Function", type: "string" },
|
|
13
20
|
$src: {
|
|
14
21
|
description: "External module specifier. Mutually exclusive with body.",
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
-
"@webref/css": "^8.7.
|
|
4
|
-
"@webref/elements": "^2.
|
|
5
|
-
"@webref/idl": "^3.
|
|
3
|
+
"@webref/css": "^8.7.3",
|
|
4
|
+
"@webref/elements": "^2.8.0",
|
|
5
|
+
"@webref/idl": "^3.83.0",
|
|
6
6
|
"ajv": "^8.20.0",
|
|
7
7
|
"ajv-formats": "^3.0.1"
|
|
8
8
|
},
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"schema": "bun run src/schema.ts",
|
|
53
53
|
"test": "bun test --isolate",
|
|
54
54
|
"test:coverage": "bun test --isolate --coverage",
|
|
55
|
-
"upgrade": "
|
|
55
|
+
"upgrade": "bun update --latest && bun install"
|
|
56
56
|
},
|
|
57
57
|
"type": "module",
|
|
58
|
-
"version": "1.
|
|
58
|
+
"version": "2.1.0",
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"json-schema-to-ts": "^3.1.1"
|
|
61
61
|
}
|
package/schema.json
CHANGED
|
@@ -211,6 +211,10 @@
|
|
|
211
211
|
"description": "Named export in $src module. Defaults to the state key name.",
|
|
212
212
|
"type": "string"
|
|
213
213
|
},
|
|
214
|
+
"$lazy": {
|
|
215
|
+
"description": "Load $src on first call instead of importing it up front. The function then returns a promise, so it may only be called from a handler or lifecycle hook — never bound as a computed value. Use it for code most visitors never reach.",
|
|
216
|
+
"type": "boolean"
|
|
217
|
+
},
|
|
214
218
|
"$prototype": {
|
|
215
219
|
"const": "Function",
|
|
216
220
|
"type": "string"
|
|
@@ -558,7 +562,7 @@
|
|
|
558
562
|
"type": "object"
|
|
559
563
|
},
|
|
560
564
|
"ClassMethodDef": {
|
|
561
|
-
"description": "A class method or accessor definition. Capability roles (parse, serialize, discover, load) mark static methods that hosts (compiler, server, studio) invoke for format dispatch.",
|
|
565
|
+
"description": "A class method or accessor definition. Capability roles (parse, serialize, rewrite, discover, load) mark static methods that hosts (compiler, server, studio) invoke for format dispatch.",
|
|
562
566
|
"properties": {
|
|
563
567
|
"$prototype": {
|
|
564
568
|
"const": "Function",
|
|
@@ -628,6 +632,7 @@
|
|
|
628
632
|
"accessor",
|
|
629
633
|
"parse",
|
|
630
634
|
"serialize",
|
|
635
|
+
"rewrite",
|
|
631
636
|
"discover",
|
|
632
637
|
"load",
|
|
633
638
|
"projectData",
|
|
@@ -2565,6 +2570,20 @@
|
|
|
2565
2570
|
}
|
|
2566
2571
|
]
|
|
2567
2572
|
},
|
|
2573
|
+
"onconfigurationchange": {
|
|
2574
|
+
"description": "Event handler for the \"configurationchange\" event.",
|
|
2575
|
+
"oneOf": [
|
|
2576
|
+
{
|
|
2577
|
+
"$ref": "#/$defs/RefObject"
|
|
2578
|
+
},
|
|
2579
|
+
{
|
|
2580
|
+
"$ref": "#/$defs/ExpressionEntry"
|
|
2581
|
+
},
|
|
2582
|
+
{
|
|
2583
|
+
"$ref": "#/$defs/FunctionDef"
|
|
2584
|
+
}
|
|
2585
|
+
]
|
|
2586
|
+
},
|
|
2568
2587
|
"onconnect": {
|
|
2569
2588
|
"description": "Event handler for the \"connect\" event.",
|
|
2570
2589
|
"oneOf": [
|
|
@@ -5421,6 +5440,20 @@
|
|
|
5421
5440
|
}
|
|
5422
5441
|
]
|
|
5423
5442
|
},
|
|
5443
|
+
"onstream": {
|
|
5444
|
+
"description": "Event handler for the \"stream\" event.",
|
|
5445
|
+
"oneOf": [
|
|
5446
|
+
{
|
|
5447
|
+
"$ref": "#/$defs/RefObject"
|
|
5448
|
+
},
|
|
5449
|
+
{
|
|
5450
|
+
"$ref": "#/$defs/ExpressionEntry"
|
|
5451
|
+
},
|
|
5452
|
+
{
|
|
5453
|
+
"$ref": "#/$defs/FunctionDef"
|
|
5454
|
+
}
|
|
5455
|
+
]
|
|
5456
|
+
},
|
|
5424
5457
|
"onsubmit": {
|
|
5425
5458
|
"description": "Event handler for the \"submit\" event.",
|
|
5426
5459
|
"oneOf": [
|
|
@@ -22776,6 +22809,7 @@
|
|
|
22776
22809
|
"body",
|
|
22777
22810
|
"br",
|
|
22778
22811
|
"button",
|
|
22812
|
+
"camera",
|
|
22779
22813
|
"canvas",
|
|
22780
22814
|
"caption",
|
|
22781
22815
|
"circle",
|
|
@@ -22872,6 +22906,7 @@
|
|
|
22872
22906
|
"meter",
|
|
22873
22907
|
"mfrac",
|
|
22874
22908
|
"mi",
|
|
22909
|
+
"microphone",
|
|
22875
22910
|
"mmultiscripts",
|
|
22876
22911
|
"mn",
|
|
22877
22912
|
"mo",
|
|
@@ -22960,6 +22995,7 @@
|
|
|
22960
22995
|
"u",
|
|
22961
22996
|
"ul",
|
|
22962
22997
|
"use",
|
|
22998
|
+
"usermedia",
|
|
22963
22999
|
"var",
|
|
22964
23000
|
"video",
|
|
22965
23001
|
"view",
|
package/src/asset-paths.ts
CHANGED
|
@@ -538,3 +538,89 @@ export function formatSrcset(candidates: readonly SrcsetCandidate[]): string {
|
|
|
538
538
|
.map(({ url, descriptor }) => (descriptor ? `${url} ${descriptor}` : url))
|
|
539
539
|
.join(", ");
|
|
540
540
|
}
|
|
541
|
+
|
|
542
|
+
// ─── Deployment base path (RFC 3986 §5) ──────────────────────────────────────
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* The path a site is deployed under, derived from `project.json`'s `url`.
|
|
546
|
+
*
|
|
547
|
+
* **There is no second key for this, on purpose.** Every URL the build emits is a URI reference,
|
|
548
|
+
* and `url` is the base URI it is resolved against (RFC 3986 §5). A site at
|
|
549
|
+
* `https://example.pages.dev/m/my-site/` has already said where it lives; a `build.base` beside it
|
|
550
|
+
* would be a second writer on one fact, free to disagree with the canonical links and the sitemap
|
|
551
|
+
* that are built from the same value.
|
|
552
|
+
*
|
|
553
|
+
* What went wrong was narrower than a missing option: the compiler resolved its references against
|
|
554
|
+
* the base's ORIGIN and discarded its path, which is what §5.2 does to an absolute-path reference
|
|
555
|
+
* (`/assets/x.js`) and is not what a deployment means by one. So the fix is to stop discarding it.
|
|
556
|
+
*
|
|
557
|
+
* Returns `""` for a site at an origin root, a missing `url`, or a `url` that will not parse — all
|
|
558
|
+
* three mean "prefix nothing", and a malformed URL must not take the build down when the only thing
|
|
559
|
+
* it can affect is a prefix. Never has a trailing slash, so `base + "/assets/x"` is well-formed.
|
|
560
|
+
*
|
|
561
|
+
* @param {string | undefined} url - `project.json`'s `url`
|
|
562
|
+
* @returns {string} `""` or a path with a leading slash and no trailing one
|
|
563
|
+
*/
|
|
564
|
+
export function siteBasePath(url: string | undefined | null): string {
|
|
565
|
+
if (typeof url !== "string" || url === "") {
|
|
566
|
+
return "";
|
|
567
|
+
}
|
|
568
|
+
let pathname: string;
|
|
569
|
+
try {
|
|
570
|
+
({ pathname } = new URL(url));
|
|
571
|
+
} catch {
|
|
572
|
+
return "";
|
|
573
|
+
}
|
|
574
|
+
const trimmed = pathname.replace(/\/+$/, "");
|
|
575
|
+
if (trimmed === "" || trimmed === "/") {
|
|
576
|
+
return "";
|
|
577
|
+
}
|
|
578
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Re-root a site-absolute URL onto the deployment base.
|
|
583
|
+
*
|
|
584
|
+
* Only an absolute-path reference is affected — the one form whose meaning is "from the site root",
|
|
585
|
+
* and therefore the one form that moves when the site root does. A protocol-relative `//host/x`, an
|
|
586
|
+
* absolute URL, a fragment, a query and every relative path already resolve against something else
|
|
587
|
+
* and are returned untouched.
|
|
588
|
+
*
|
|
589
|
+
* **Idempotent**, because a URL may reach this more than once: an emitter that already prefixed and
|
|
590
|
+
* an output pass that prefixes everything must not compound. A value already under the base is
|
|
591
|
+
* returned as-is.
|
|
592
|
+
*
|
|
593
|
+
* @param {string} base - From {@link siteBasePath}
|
|
594
|
+
* @param {string} url - A URL as emitted
|
|
595
|
+
* @returns {string} The URL a browser should be given
|
|
596
|
+
*/
|
|
597
|
+
export function withBase(base: string, url: string): string {
|
|
598
|
+
if (base === "" || !url.startsWith("/") || url.startsWith("//")) {
|
|
599
|
+
return url;
|
|
600
|
+
}
|
|
601
|
+
if (url === base || url.startsWith(`${base}/`)) {
|
|
602
|
+
return url;
|
|
603
|
+
}
|
|
604
|
+
return base + url;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* An absolute URL for a site route, keeping the base's path.
|
|
609
|
+
*
|
|
610
|
+
* `new URL("/about", "https://x.dev/m/site/")` is `https://x.dev/about` — RFC 3986 §5.2 replaces
|
|
611
|
+
* the whole path for an absolute-path reference, which is exactly right for the RFC and exactly
|
|
612
|
+
* wrong for a canonical link. Resolving the route as a RELATIVE-path reference keeps the base, so
|
|
613
|
+
* `<link rel="canonical">`, `sitemap.xml` and `robots.txt` name pages that exist.
|
|
614
|
+
*
|
|
615
|
+
* @param {string} route - A site route (`/about`), leading slash optional
|
|
616
|
+
* @param {string} siteUrl - `project.json`'s `url`
|
|
617
|
+
* @returns {string} The absolute URL
|
|
618
|
+
*/
|
|
619
|
+
export function siteAbsoluteUrl(route: string, siteUrl: string): string {
|
|
620
|
+
const base = new URL(siteUrl);
|
|
621
|
+
// A base whose path does not end in "/" would drop its last segment under §5.2's merge.
|
|
622
|
+
if (!base.pathname.endsWith("/")) {
|
|
623
|
+
base.pathname += "/";
|
|
624
|
+
}
|
|
625
|
+
return new URL(route.replace(/^\/+/, ""), base).href;
|
|
626
|
+
}
|
package/src/format-registry.ts
CHANGED
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
|
|
13
13
|
import { mediaTypeEssence, mediaTypeProblem } from "./media-type";
|
|
14
14
|
|
|
15
|
-
export type FormatCapability = "parse" | "serialize" | "discover" | "load";
|
|
15
|
+
export type FormatCapability = "parse" | "serialize" | "rewrite" | "discover" | "load";
|
|
16
16
|
|
|
17
17
|
export const FORMAT_CAPABILITIES: readonly FormatCapability[] = [
|
|
18
18
|
"parse",
|
|
19
19
|
"serialize",
|
|
20
|
+
"rewrite",
|
|
20
21
|
"discover",
|
|
21
22
|
"load",
|
|
22
23
|
];
|
|
@@ -27,6 +28,13 @@ export const FORMAT_CAPABILITIES: readonly FormatCapability[] = [
|
|
|
27
28
|
* asset emission (`emit`), `<head>` contribution (`head`), static asset mounts (`assets`),
|
|
28
29
|
* server-mount (`mount`), and connector (`dialect`, `deploySchema`, `bindings`, `testConnection`)
|
|
29
30
|
* roles.
|
|
31
|
+
*
|
|
32
|
+
* `rewrite` is the narrow sibling of `serialize`, and the two are not ranked: a format may declare
|
|
33
|
+
* either, both or neither. `serialize` says "this document round-trips through me"; `rewrite` says
|
|
34
|
+
* only "I can replace these authored values in my own source text and change nothing else". A
|
|
35
|
+
* load-only format — a CSV collection is read to load entries, never re-emitted as a document — can
|
|
36
|
+
* honestly promise the second and not the first, which is enough for a rename to repair a reference
|
|
37
|
+
* living inside it (specs/extensions.md §8).
|
|
30
38
|
*/
|
|
31
39
|
export type ExtensionCapability =
|
|
32
40
|
| FormatCapability
|
package/src/locale.ts
CHANGED
|
@@ -427,3 +427,27 @@ export function primaryLanguage(tag: unknown): string | null {
|
|
|
427
427
|
const canonical = canonicalizeLocale(tag);
|
|
428
428
|
return canonical === null ? null : (canonical.split("-")[0] ?? null);
|
|
429
429
|
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* The locale a URL belongs to.
|
|
433
|
+
*
|
|
434
|
+
* Matching is on the **first path segment**, compared canonically, so `/fr-CA/about/` resolves for
|
|
435
|
+
* a project that declared `fr-ca`. A path under no declared locale is the default locale — under
|
|
436
|
+
* `prefix-except-default` that is the point, and under `prefix-always` it is a page the author put
|
|
437
|
+
* outside the locale tree, which is theirs to place.
|
|
438
|
+
*
|
|
439
|
+
* @param {string} urlPattern - Site-absolute route pattern, e.g. `/fr/about/`
|
|
440
|
+
* @param {ResolvedI18n | null} i18n
|
|
441
|
+
* @returns {string | null} A canonical tag, or null when the project has no i18n
|
|
442
|
+
*/
|
|
443
|
+
export function localeOfRoute(urlPattern: string, i18n: ResolvedI18n | null): string | null {
|
|
444
|
+
if (i18n === null) {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
const first = urlPattern.split("/").find(Boolean);
|
|
448
|
+
if (first === undefined) {
|
|
449
|
+
return i18n.defaultLocale;
|
|
450
|
+
}
|
|
451
|
+
const canonical = canonicalizeLocale(first);
|
|
452
|
+
return canonical !== null && i18n.locales.includes(canonical) ? canonical : i18n.defaultLocale;
|
|
453
|
+
}
|
package/types.ts
CHANGED
|
@@ -171,6 +171,13 @@ export interface JxFunctionDef {
|
|
|
171
171
|
$src?: string;
|
|
172
172
|
/** Named export within `$src`; defaults to the state key. */
|
|
173
173
|
$export?: string;
|
|
174
|
+
/**
|
|
175
|
+
* Load `$src` on first call rather than importing it with the module.
|
|
176
|
+
*
|
|
177
|
+
* The function returns a promise once lazy, so it may only be called — never bound as a computed
|
|
178
|
+
* value. Meant for code most visitors never reach.
|
|
179
|
+
*/
|
|
180
|
+
$lazy?: boolean;
|
|
174
181
|
/** Legacy alias for `parameters` (bare names only). */
|
|
175
182
|
arguments?: string[];
|
|
176
183
|
timing?: "compiler" | "server" | "client";
|
|
@@ -648,6 +655,13 @@ export interface ProjectConfig {
|
|
|
648
655
|
deploy?: DeployConfig;
|
|
649
656
|
sitemap?: boolean;
|
|
650
657
|
headers?: HeadersConfig;
|
|
658
|
+
/**
|
|
659
|
+
* Minify emitted browser JavaScript. Defaults to true.
|
|
660
|
+
*
|
|
661
|
+
* Set false to ship readable bundles — debugging a deployed page, or diffing `dist/` where the
|
|
662
|
+
* two bundler backends agree on semantics but not on minified bytes.
|
|
663
|
+
*/
|
|
664
|
+
minify?: boolean;
|
|
651
665
|
[key: string]: unknown;
|
|
652
666
|
};
|
|
653
667
|
images?: ImageConfig;
|