@jxsuite/schema 2.0.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/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",
@@ -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" },
package/package.json CHANGED
@@ -55,7 +55,7 @@
55
55
  "upgrade": "bun update --latest && bun install"
56
56
  },
57
57
  "type": "module",
58
- "version": "2.0.0",
58
+ "version": "2.1.0",
59
59
  "devDependencies": {
60
60
  "json-schema-to-ts": "^3.1.1"
61
61
  }
package/schema.json CHANGED
@@ -562,7 +562,7 @@
562
562
  "type": "object"
563
563
  },
564
564
  "ClassMethodDef": {
565
- "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.",
566
566
  "properties": {
567
567
  "$prototype": {
568
568
  "const": "Function",
@@ -632,6 +632,7 @@
632
632
  "accessor",
633
633
  "parse",
634
634
  "serialize",
635
+ "rewrite",
635
636
  "discover",
636
637
  "load",
637
638
  "projectData",
@@ -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
+ }
@@ -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