@valbuild/shared 0.134.0 → 0.136.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/CHANGELOG.md CHANGED
@@ -1,5 +1,77 @@
1
1
  # @valbuild/shared
2
2
 
3
+ ## 0.136.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#713](https://github.com/valbuild/val/pull/713) [`f3a4bb7`](https://github.com/valbuild/val/commit/f3a4bb7aea604943b92545c122243798c759715c) Thanks [@freekh](https://github.com/freekh)! - The Studio can load a bundler, and it stops telling a managed project that its
8
+ publish is on its way out.
9
+
10
+ **A managed project is one with no repository and no host watching one.** There
11
+ is nothing outside the browser to pick a commit up, so the Studio has been
12
+ showing it a story that belongs to a project with a repository: a `Building`
13
+ spinner, waiting for an event that can never arrive. It does not resolve on a
14
+ reload, on a retry, or tomorrow, and there is no way to tell it from a deploy
15
+ that is merely slow.
16
+
17
+ The content service now reports which kind of project this is, as `sourceMode`
18
+ on `/stat`, and the Studio narrates accordingly:
19
+
20
+ - a **connected** project keeps the deploy feed and keeps `Building`, because
21
+ there a host genuinely does pick the commit up;
22
+ - a **managed** one says `Saved, not yet live` instead — a durable condition,
23
+ not a phase, because nothing else will ever resolve it.
24
+
25
+ A server that reports no source mode keeps the behaviour it has today. Absence
26
+ means "not reported", never "managed": `fs` mode has no project to have a mode,
27
+ and neither does a content service that predates the field.
28
+
29
+ **The Studio also loads `@valbuild/tanstack-build` in the tab**, at mount and at
30
+ idle, for the managed projects that will need it — a browser that is the
31
+ deployer needs a bundler. This is the groundwork for browser-side publishing;
32
+ the publish path itself is unchanged in this release.
33
+
34
+ That bundler is `@rolldown/browser`, whose WebAssembly binary is 10.9 MB and is
35
+ **not** in the npm package. It is served from `static.val.build`, addressed by
36
+ the SHA-256 of its own bytes, so the build and the binary it needs cannot drift
37
+ apart. A deployment that must not reach that host — an air-gapped install, a
38
+ mirror — sets `globalThis.__VAL_ROLLDOWN_WASM_URL__` before the Studio loads and
39
+ needs no rebuild.
40
+
41
+ **Building in the browser requires a cross-origin isolated page.** Rolldown runs
42
+ WebAssembly on worker threads that share memory, and a browser will not hand a
43
+ `SharedArrayBuffer` to a worker otherwise. Without
44
+ `Cross-Origin-Opener-Policy: same-origin` and
45
+ `Cross-Origin-Embedder-Policy: require-corp` on the document Val is mounted in,
46
+ loading the bundler now fails with a message that says exactly that, instead of
47
+ a `DataCloneError` thrown from inside a worker. Nothing else in this release is
48
+ affected: a Studio that never builds in the browser never asks.
49
+
50
+ ### Patch Changes
51
+
52
+ - Updated dependencies [[`f3a4bb7`](https://github.com/valbuild/val/commit/f3a4bb7aea604943b92545c122243798c759715c)]:
53
+ - @valbuild/core@0.136.0
54
+
55
+ ## 0.134.1
56
+
57
+ ### Patch Changes
58
+
59
+ - [#709](https://github.com/valbuild/val/pull/709) [`821e789`](https://github.com/valbuild/val/commit/821e789c1af47510af5d23eb96384ff78ddd7646) Thanks [@freekh](https://github.com/freekh)! - Show the reason behind a content-service error instead of just "Internal Server
60
+ Error".
61
+
62
+ `content.val.build` answers a handler that threw with `{ message: "Internal
63
+ Server Error", details: "<the actual cause>" }`. Val was reading `message` and
64
+ dropping `details`, so a failed save put exactly this in front of an editor —
65
+ and the same text in the app's own logs, because Val was relaying it:
66
+
67
+ ```json
68
+ { "type": "patch-error", "message": "Internal Server Error", "errors": { … } }
69
+ ```
70
+
71
+ A string `details` is now appended to the message (capped at 500 characters).
72
+ A structured one is not: that is what a _refusal_ carries, and its summary is
73
+ already the message.
74
+
3
75
  ## 0.134.0
4
76
 
5
77
  ### Minor Changes
@@ -423,6 +423,7 @@ export declare const Api: {
423
423
  profileId: z.ZodNullable<z.ZodString>;
424
424
  mode: z.ZodUnion<readonly [z.ZodLiteral<"http">, z.ZodLiteral<"fs">]>;
425
425
  jsonEntriesSha: z.ZodOptional<z.ZodString>;
426
+ sourceMode: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"managed">, z.ZodLiteral<"connected">]>>;
426
427
  publishRefusal: z.ZodOptional<z.ZodObject<{
427
428
  code: z.ZodLiteral<"no-base">;
428
429
  message: z.ZodString;
@@ -466,6 +467,7 @@ export declare const Api: {
466
467
  }, z.core.$strip>;
467
468
  profileId: z.ZodNullable<z.ZodString>;
468
469
  mode: z.ZodUnion<readonly [z.ZodLiteral<"http">, z.ZodLiteral<"fs">]>;
470
+ sourceMode: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"managed">, z.ZodLiteral<"connected">]>>;
469
471
  publishRefusal: z.ZodOptional<z.ZodObject<{
470
472
  code: z.ZodLiteral<"no-base">;
471
473
  message: z.ZodString;
@@ -1864,6 +1864,27 @@ var Api = {
1864
1864
  profileId: zod.z.string().nullable(),
1865
1865
  mode: zod.z.union([zod.z.literal("http"), zod.z.literal("fs")]),
1866
1866
  jsonEntriesSha: zod.z.string().optional(),
1867
+ /**
1868
+ * How this project's SOURCE is kept, when the server knows.
1869
+ *
1870
+ * `"managed"` -- the content service is the store of record,
1871
+ * there is no repository, and the Studio is the deployer: the
1872
+ * build that makes a publish live happens in the tab that started
1873
+ * it. `"connected"` -- commits are mirrored into a repository a
1874
+ * host watches, so something outside the browser finishes the
1875
+ * job.
1876
+ *
1877
+ * The Studio narrates a publish differently for each, because a
1878
+ * `building` state in managed mode is a spinner with no event
1879
+ * that can ever end it -- not on a reload, not on a retry, not
1880
+ * tomorrow.
1881
+ *
1882
+ * Optional, and absent means "not reported", never "managed":
1883
+ * `fs` mode has no project to have a mode, and so does an `http`
1884
+ * server that predates the field. Both keep the connected story,
1885
+ * which is the one the Studio has always told.
1886
+ */
1887
+ sourceMode: zod.z.union([zod.z.literal("managed"), zod.z.literal("connected")]).optional(),
1867
1888
  publishRefusal: PublishRefusal.optional()
1868
1889
  }), zod.z.object({
1869
1890
  type: zod.z.literal("use-websocket"),
@@ -1926,6 +1947,27 @@ var Api = {
1926
1947
  config: ValConfig,
1927
1948
  profileId: zod.z.string().nullable(),
1928
1949
  mode: zod.z.union([zod.z.literal("http"), zod.z.literal("fs")]),
1950
+ /**
1951
+ * How this project's SOURCE is kept, when the server knows.
1952
+ *
1953
+ * `"managed"` -- the content service is the store of record,
1954
+ * there is no repository, and the Studio is the deployer: the
1955
+ * build that makes a publish live happens in the tab that started
1956
+ * it. `"connected"` -- commits are mirrored into a repository a
1957
+ * host watches, so something outside the browser finishes the
1958
+ * job.
1959
+ *
1960
+ * The Studio narrates a publish differently for each, because a
1961
+ * `building` state in managed mode is a spinner with no event
1962
+ * that can ever end it -- not on a reload, not on a retry, not
1963
+ * tomorrow.
1964
+ *
1965
+ * Optional, and absent means "not reported", never "managed":
1966
+ * `fs` mode has no project to have a mode, and so does an `http`
1967
+ * server that predates the field. Both keep the connected story,
1968
+ * which is the one the Studio has always told.
1969
+ */
1970
+ sourceMode: zod.z.union([zod.z.literal("managed"), zod.z.literal("connected")]).optional(),
1929
1971
  publishRefusal: PublishRefusal.optional()
1930
1972
  })])
1931
1973
  })])
@@ -3057,12 +3099,59 @@ var Api = {
3057
3099
  // `number` is allowed because the client stringifies every query value before it
3058
3100
  // goes on the URL (see createValClient), so a numeric param round-trips fine.
3059
3101
 
3102
+ /**
3103
+ * How long an appended `details` may be before it is cut.
3104
+ *
3105
+ * A thrown DAL error in the content service quotes the whole offending ROW back
3106
+ * — which for a patch is every op the editor just made — so an uncapped append
3107
+ * puts kilobytes of JSON in a dialog. The head is where the cause is; the tail
3108
+ * is the evidence, and it belongs in the service's own logs.
3109
+ */
3110
+ var MAX_DETAILS_LENGTH = 500;
3060
3111
  function getErrorMessageFromUnknownJson(json, fallback) {
3061
3112
  if (getSitemapTree._typeof(json) === "object" && json !== null && "message" in json && typeof json.message === "string") {
3062
- return json.message;
3113
+ var details = detailsOf(json);
3114
+ if (details === null || details === json.message) {
3115
+ return json.message;
3116
+ }
3117
+ return "".concat(json.message, ": ").concat(details);
3063
3118
  }
3064
3119
  return fallback;
3065
3120
  }
3121
+
3122
+ /**
3123
+ * The cause the content service put beside its message, when it wrote one a
3124
+ * person can read.
3125
+ *
3126
+ * `HttpError` in `home` is `{ statusCode, message?, details? }`, and `details`
3127
+ * is filled in two quite different ways. Where a handler REFUSED, it holds the
3128
+ * structured zod error whose summary is already the `message` — repeating it
3129
+ * adds nothing. Where a handler THREW, `sendResult` writes `message:
3130
+ * "Internal Server Error"` and puts the exception's own message in `details` —
3131
+ * so there the message says nothing and the details say everything.
3132
+ *
3133
+ * Which is why the test is the TYPE rather than the status code: a string
3134
+ * `details` is prose meant to be read, an object is a payload meant to be
3135
+ * parsed. This takes the first and leaves the second.
3136
+ *
3137
+ * It was written after an hour was lost to the second case. A patch save
3138
+ * against a content service that threw showed the Studio exactly
3139
+ * `"Internal Server Error"` and nothing else, in a dialog with no other clue,
3140
+ * while the service's answer had been carrying the reason the whole time.
3141
+ */
3142
+ function detailsOf(json) {
3143
+ if (!("details" in json) || typeof json.details !== "string") {
3144
+ return null;
3145
+ }
3146
+ var details = json.details.trim();
3147
+ if (details === "") {
3148
+ return null;
3149
+ }
3150
+ if (details.length <= MAX_DETAILS_LENGTH) {
3151
+ return details;
3152
+ }
3153
+ return "".concat(details.slice(0, MAX_DETAILS_LENGTH), "\u2026");
3154
+ }
3066
3155
  var DEFAULT_ERROR_MESSAGE = "Unknown error with unknown message from server";
3067
3156
 
3068
3157
  var createValClient = function createValClient(host, config // We want to use this in the future
@@ -1864,6 +1864,27 @@ var Api = {
1864
1864
  profileId: zod.z.string().nullable(),
1865
1865
  mode: zod.z.union([zod.z.literal("http"), zod.z.literal("fs")]),
1866
1866
  jsonEntriesSha: zod.z.string().optional(),
1867
+ /**
1868
+ * How this project's SOURCE is kept, when the server knows.
1869
+ *
1870
+ * `"managed"` -- the content service is the store of record,
1871
+ * there is no repository, and the Studio is the deployer: the
1872
+ * build that makes a publish live happens in the tab that started
1873
+ * it. `"connected"` -- commits are mirrored into a repository a
1874
+ * host watches, so something outside the browser finishes the
1875
+ * job.
1876
+ *
1877
+ * The Studio narrates a publish differently for each, because a
1878
+ * `building` state in managed mode is a spinner with no event
1879
+ * that can ever end it -- not on a reload, not on a retry, not
1880
+ * tomorrow.
1881
+ *
1882
+ * Optional, and absent means "not reported", never "managed":
1883
+ * `fs` mode has no project to have a mode, and so does an `http`
1884
+ * server that predates the field. Both keep the connected story,
1885
+ * which is the one the Studio has always told.
1886
+ */
1887
+ sourceMode: zod.z.union([zod.z.literal("managed"), zod.z.literal("connected")]).optional(),
1867
1888
  publishRefusal: PublishRefusal.optional()
1868
1889
  }), zod.z.object({
1869
1890
  type: zod.z.literal("use-websocket"),
@@ -1926,6 +1947,27 @@ var Api = {
1926
1947
  config: ValConfig,
1927
1948
  profileId: zod.z.string().nullable(),
1928
1949
  mode: zod.z.union([zod.z.literal("http"), zod.z.literal("fs")]),
1950
+ /**
1951
+ * How this project's SOURCE is kept, when the server knows.
1952
+ *
1953
+ * `"managed"` -- the content service is the store of record,
1954
+ * there is no repository, and the Studio is the deployer: the
1955
+ * build that makes a publish live happens in the tab that started
1956
+ * it. `"connected"` -- commits are mirrored into a repository a
1957
+ * host watches, so something outside the browser finishes the
1958
+ * job.
1959
+ *
1960
+ * The Studio narrates a publish differently for each, because a
1961
+ * `building` state in managed mode is a spinner with no event
1962
+ * that can ever end it -- not on a reload, not on a retry, not
1963
+ * tomorrow.
1964
+ *
1965
+ * Optional, and absent means "not reported", never "managed":
1966
+ * `fs` mode has no project to have a mode, and so does an `http`
1967
+ * server that predates the field. Both keep the connected story,
1968
+ * which is the one the Studio has always told.
1969
+ */
1970
+ sourceMode: zod.z.union([zod.z.literal("managed"), zod.z.literal("connected")]).optional(),
1929
1971
  publishRefusal: PublishRefusal.optional()
1930
1972
  })])
1931
1973
  })])
@@ -3057,12 +3099,59 @@ var Api = {
3057
3099
  // `number` is allowed because the client stringifies every query value before it
3058
3100
  // goes on the URL (see createValClient), so a numeric param round-trips fine.
3059
3101
 
3102
+ /**
3103
+ * How long an appended `details` may be before it is cut.
3104
+ *
3105
+ * A thrown DAL error in the content service quotes the whole offending ROW back
3106
+ * — which for a patch is every op the editor just made — so an uncapped append
3107
+ * puts kilobytes of JSON in a dialog. The head is where the cause is; the tail
3108
+ * is the evidence, and it belongs in the service's own logs.
3109
+ */
3110
+ var MAX_DETAILS_LENGTH = 500;
3060
3111
  function getErrorMessageFromUnknownJson(json, fallback) {
3061
3112
  if (getSitemapTree._typeof(json) === "object" && json !== null && "message" in json && typeof json.message === "string") {
3062
- return json.message;
3113
+ var details = detailsOf(json);
3114
+ if (details === null || details === json.message) {
3115
+ return json.message;
3116
+ }
3117
+ return "".concat(json.message, ": ").concat(details);
3063
3118
  }
3064
3119
  return fallback;
3065
3120
  }
3121
+
3122
+ /**
3123
+ * The cause the content service put beside its message, when it wrote one a
3124
+ * person can read.
3125
+ *
3126
+ * `HttpError` in `home` is `{ statusCode, message?, details? }`, and `details`
3127
+ * is filled in two quite different ways. Where a handler REFUSED, it holds the
3128
+ * structured zod error whose summary is already the `message` — repeating it
3129
+ * adds nothing. Where a handler THREW, `sendResult` writes `message:
3130
+ * "Internal Server Error"` and puts the exception's own message in `details` —
3131
+ * so there the message says nothing and the details say everything.
3132
+ *
3133
+ * Which is why the test is the TYPE rather than the status code: a string
3134
+ * `details` is prose meant to be read, an object is a payload meant to be
3135
+ * parsed. This takes the first and leaves the second.
3136
+ *
3137
+ * It was written after an hour was lost to the second case. A patch save
3138
+ * against a content service that threw showed the Studio exactly
3139
+ * `"Internal Server Error"` and nothing else, in a dialog with no other clue,
3140
+ * while the service's answer had been carrying the reason the whole time.
3141
+ */
3142
+ function detailsOf(json) {
3143
+ if (!("details" in json) || typeof json.details !== "string") {
3144
+ return null;
3145
+ }
3146
+ var details = json.details.trim();
3147
+ if (details === "") {
3148
+ return null;
3149
+ }
3150
+ if (details.length <= MAX_DETAILS_LENGTH) {
3151
+ return details;
3152
+ }
3153
+ return "".concat(details.slice(0, MAX_DETAILS_LENGTH), "\u2026");
3154
+ }
3066
3155
  var DEFAULT_ERROR_MESSAGE = "Unknown error with unknown message from server";
3067
3156
 
3068
3157
  var createValClient = function createValClient(host, config // We want to use this in the future
@@ -1857,6 +1857,27 @@ var Api = {
1857
1857
  profileId: z.string().nullable(),
1858
1858
  mode: z.union([z.literal("http"), z.literal("fs")]),
1859
1859
  jsonEntriesSha: z.string().optional(),
1860
+ /**
1861
+ * How this project's SOURCE is kept, when the server knows.
1862
+ *
1863
+ * `"managed"` -- the content service is the store of record,
1864
+ * there is no repository, and the Studio is the deployer: the
1865
+ * build that makes a publish live happens in the tab that started
1866
+ * it. `"connected"` -- commits are mirrored into a repository a
1867
+ * host watches, so something outside the browser finishes the
1868
+ * job.
1869
+ *
1870
+ * The Studio narrates a publish differently for each, because a
1871
+ * `building` state in managed mode is a spinner with no event
1872
+ * that can ever end it -- not on a reload, not on a retry, not
1873
+ * tomorrow.
1874
+ *
1875
+ * Optional, and absent means "not reported", never "managed":
1876
+ * `fs` mode has no project to have a mode, and so does an `http`
1877
+ * server that predates the field. Both keep the connected story,
1878
+ * which is the one the Studio has always told.
1879
+ */
1880
+ sourceMode: z.union([z.literal("managed"), z.literal("connected")]).optional(),
1860
1881
  publishRefusal: PublishRefusal.optional()
1861
1882
  }), z.object({
1862
1883
  type: z.literal("use-websocket"),
@@ -1919,6 +1940,27 @@ var Api = {
1919
1940
  config: ValConfig,
1920
1941
  profileId: z.string().nullable(),
1921
1942
  mode: z.union([z.literal("http"), z.literal("fs")]),
1943
+ /**
1944
+ * How this project's SOURCE is kept, when the server knows.
1945
+ *
1946
+ * `"managed"` -- the content service is the store of record,
1947
+ * there is no repository, and the Studio is the deployer: the
1948
+ * build that makes a publish live happens in the tab that started
1949
+ * it. `"connected"` -- commits are mirrored into a repository a
1950
+ * host watches, so something outside the browser finishes the
1951
+ * job.
1952
+ *
1953
+ * The Studio narrates a publish differently for each, because a
1954
+ * `building` state in managed mode is a spinner with no event
1955
+ * that can ever end it -- not on a reload, not on a retry, not
1956
+ * tomorrow.
1957
+ *
1958
+ * Optional, and absent means "not reported", never "managed":
1959
+ * `fs` mode has no project to have a mode, and so does an `http`
1960
+ * server that predates the field. Both keep the connected story,
1961
+ * which is the one the Studio has always told.
1962
+ */
1963
+ sourceMode: z.union([z.literal("managed"), z.literal("connected")]).optional(),
1922
1964
  publishRefusal: PublishRefusal.optional()
1923
1965
  })])
1924
1966
  })])
@@ -3050,12 +3092,59 @@ var Api = {
3050
3092
  // `number` is allowed because the client stringifies every query value before it
3051
3093
  // goes on the URL (see createValClient), so a numeric param round-trips fine.
3052
3094
 
3095
+ /**
3096
+ * How long an appended `details` may be before it is cut.
3097
+ *
3098
+ * A thrown DAL error in the content service quotes the whole offending ROW back
3099
+ * — which for a patch is every op the editor just made — so an uncapped append
3100
+ * puts kilobytes of JSON in a dialog. The head is where the cause is; the tail
3101
+ * is the evidence, and it belongs in the service's own logs.
3102
+ */
3103
+ var MAX_DETAILS_LENGTH = 500;
3053
3104
  function getErrorMessageFromUnknownJson(json, fallback) {
3054
3105
  if (_typeof(json) === "object" && json !== null && "message" in json && typeof json.message === "string") {
3055
- return json.message;
3106
+ var details = detailsOf(json);
3107
+ if (details === null || details === json.message) {
3108
+ return json.message;
3109
+ }
3110
+ return "".concat(json.message, ": ").concat(details);
3056
3111
  }
3057
3112
  return fallback;
3058
3113
  }
3114
+
3115
+ /**
3116
+ * The cause the content service put beside its message, when it wrote one a
3117
+ * person can read.
3118
+ *
3119
+ * `HttpError` in `home` is `{ statusCode, message?, details? }`, and `details`
3120
+ * is filled in two quite different ways. Where a handler REFUSED, it holds the
3121
+ * structured zod error whose summary is already the `message` — repeating it
3122
+ * adds nothing. Where a handler THREW, `sendResult` writes `message:
3123
+ * "Internal Server Error"` and puts the exception's own message in `details` —
3124
+ * so there the message says nothing and the details say everything.
3125
+ *
3126
+ * Which is why the test is the TYPE rather than the status code: a string
3127
+ * `details` is prose meant to be read, an object is a payload meant to be
3128
+ * parsed. This takes the first and leaves the second.
3129
+ *
3130
+ * It was written after an hour was lost to the second case. A patch save
3131
+ * against a content service that threw showed the Studio exactly
3132
+ * `"Internal Server Error"` and nothing else, in a dialog with no other clue,
3133
+ * while the service's answer had been carrying the reason the whole time.
3134
+ */
3135
+ function detailsOf(json) {
3136
+ if (!("details" in json) || typeof json.details !== "string") {
3137
+ return null;
3138
+ }
3139
+ var details = json.details.trim();
3140
+ if (details === "") {
3141
+ return null;
3142
+ }
3143
+ if (details.length <= MAX_DETAILS_LENGTH) {
3144
+ return details;
3145
+ }
3146
+ return "".concat(details.slice(0, MAX_DETAILS_LENGTH), "\u2026");
3147
+ }
3059
3148
  var DEFAULT_ERROR_MESSAGE = "Unknown error with unknown message from server";
3060
3149
 
3061
3150
  var createValClient = function createValClient(host, config // We want to use this in the future
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valbuild/shared",
3
- "version": "0.134.0",
3
+ "version": "0.136.0",
4
4
  "private": false,
5
5
  "description": "Val shared types and utilities",
6
6
  "repository": {
@@ -37,7 +37,7 @@
37
37
  "flexsearch": "^0.8.212",
38
38
  "zod": "^4.4.3",
39
39
  "zod-validation-error": "^5.0.0",
40
- "@valbuild/core": "0.134.0"
40
+ "@valbuild/core": "0.136.0"
41
41
  },
42
42
  "files": [
43
43
  "CHANGELOG.md",