@hollware/server 0.11.0 → 1.0.0-rc.1

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.
Files changed (2) hide show
  1. package/dist/index.mjs +47 -10
  2. package/package.json +3 -3
package/dist/index.mjs CHANGED
@@ -10,7 +10,7 @@ import { CLIENT_CAPABILITIES_META_KEY, CLIENT_INFO_META_KEY, McpServer, accepted
10
10
  import * as z from "zod";
11
11
  import { WorkerEntrypoint } from "cloudflare:workers";
12
12
  //#region package.json
13
- var version = "0.11.0";
13
+ var version = "1.0.0-rc.1";
14
14
  //#endregion
15
15
  //#region src/auth/responses.ts
16
16
  /** The challenge of every 401 and 403 (RFC 6750). */
@@ -2458,8 +2458,24 @@ function gatewayError(status, body) {
2458
2458
  * in its fix, which is how the operator finds the line of the log that has the cause. The cause
2459
2459
  * goes to the log and never to the caller, whose agent could repeat it to a person or to another
2460
2460
  * system. This holds for every error, a failed D1 query included: nothing turns one into a 401.
2461
+ * - But a database that lacks the migrations of the release is a 503 `not_migrated` that says what
2462
+ * to run: an agent that set the project up can fix it, and the cause still goes to the log only.
2461
2463
  * - A path that no route has is a JSON 404.
2462
2464
  */
2465
+ /** What D1 says when a table or a column of this release is not there: a migration is missing. */
2466
+ const MISSING_SCHEMA = /no such (?:table|column)|has no column named/;
2467
+ /**
2468
+ * Whether an error, or one of its causes, is D1 telling that the database lacks the schema of this
2469
+ * release: the one failure whose fix the caller can be told without anything of its cause.
2470
+ */
2471
+ function isMissingMigration(error) {
2472
+ let current = error;
2473
+ for (let depth = 0; depth < 5 && current instanceof Error; depth++) {
2474
+ if (MISSING_SCHEMA.test(current.message)) return true;
2475
+ current = current.cause;
2476
+ }
2477
+ return false;
2478
+ }
2463
2479
  function installErrorHandling(app, log) {
2464
2480
  app.onError((c) => {
2465
2481
  const requestId = c.get("requestId");
@@ -2470,6 +2486,11 @@ function installErrorHandling(app, log) {
2470
2486
  path: c.req.path,
2471
2487
  detail: describeError(c.error)
2472
2488
  });
2489
+ if (isMissingMigration(c.error)) return gatewayError(503, {
2490
+ code: "not_migrated",
2491
+ message: "The database of this Hollware lacks the migrations of its release.",
2492
+ fix: "Apply them: \"hollware migrate --local\" in local development, \"hollware migrate --remote\" (or \"hollware deploy\") for a deployment. Then try again."
2493
+ });
2473
2494
  return gatewayError(500, {
2474
2495
  code: "internal",
2475
2496
  message: "The request failed on the server.",
@@ -3877,6 +3898,8 @@ function installOpsApi(app, options) {
3877
3898
  }
3878
3899
  //#endregion
3879
3900
  //#region src/http/read-api.ts
3901
+ /** The paths of the index of the read API, which answers with no token. */
3902
+ const INDEX_PATHS = /* @__PURE__ */ new Set(["/v1", "/v1/"]);
3880
3903
  /** How long a browser keeps the answer of a preflight, in seconds. */
3881
3904
  const PREFLIGHT_MAX_AGE = 600;
3882
3905
  /** The origin of a request from a page of another origin than Hollware's own, or `null`. */
@@ -3976,7 +3999,7 @@ function installReadApi(app, options) {
3976
3999
  message: "The read API only reads.",
3977
4000
  fix: "Use GET."
3978
4001
  }, 405, { allow: "GET, HEAD" });
3979
- if (c.req.path === "/v1/openapi.json") return next();
4002
+ if (c.req.path === "/v1/openapi.json" || INDEX_PATHS.has(c.req.path)) return next();
3980
4003
  c.res = await options.protect(c, async (principal) => {
3981
4004
  if (!principal.scopes.includes("content:read")) return insufficientScope(["content:read"], options.metadataUrl(c));
3982
4005
  c.set("site", principal.site);
@@ -4000,6 +4023,13 @@ function installReadApi(app, options) {
4000
4023
  return c.res;
4001
4024
  });
4002
4025
  app.get("/v1/openapi.json", (c) => c.json(openApiDocument()));
4026
+ for (const path of INDEX_PATHS) app.get(path, (c) => {
4027
+ const origin = new URL(c.env.HOLLWARE_PUBLIC_URL).origin;
4028
+ return c.json({
4029
+ openapi: `${origin}/v1/openapi.json`,
4030
+ site: `${origin}/v1/site`
4031
+ });
4032
+ });
4003
4033
  app.get("/v1/site", readRoute(async (c) => answer(c, await options.read(c.env).describeSite(c.get("site")))));
4004
4034
  app.get("/v1/search", readRoute(async (c) => {
4005
4035
  const query = c.req.query();
@@ -6825,6 +6855,19 @@ async function currentOf(context) {
6825
6855
  definition: siteDefinitionData.parse(stored.definition)
6826
6856
  };
6827
6857
  }
6858
+ /**
6859
+ * A change sent at a version that is not the current one. It says which version is, and that the
6860
+ * change was not applied: whoever changed the structure since (often the caller's own earlier
6861
+ * update_site) made the definition that was read stale.
6862
+ */
6863
+ function conflict(sent, current) {
6864
+ return {
6865
+ outcome: "conflict",
6866
+ currentVersion: current,
6867
+ message: `The structure is at version ${current}, not ${sent}: it changed since you read it, so nothing was applied.`,
6868
+ fix: `Call get_structure again, apply your change to the definition it returns (version ${current}), and send it with version ${current}. An earlier update_site or set_collection of yours also moves the version.`
6869
+ };
6870
+ }
6828
6871
  /** Replaces the definition of the site of the call with `next`, as update_site answers. */
6829
6872
  async function change(context, version, next, confirm) {
6830
6873
  const structure = structureOf(context);
@@ -6834,10 +6877,7 @@ async function change(context, version, next, confirm) {
6834
6877
  problems
6835
6878
  };
6836
6879
  const current = await currentOf(context);
6837
- if (current.version !== version) return {
6838
- outcome: "conflict",
6839
- currentVersion: current.version
6840
- };
6880
+ if (current.version !== version) return conflict(version, current.version);
6841
6881
  if (canonicalJson(current.definition) === canonicalJson(next)) return {
6842
6882
  outcome: "unchanged",
6843
6883
  version
@@ -6859,10 +6899,7 @@ async function change(context, version, next, confirm) {
6859
6899
  definition: next,
6860
6900
  actor: actor(context),
6861
6901
  event
6862
- }) === "conflict") return {
6863
- outcome: "conflict",
6864
- currentVersion: (await currentOf(context)).version
6865
- };
6902
+ }) === "conflict") return conflict(version, (await currentOf(context)).version);
6866
6903
  context.notify?.([event.id]);
6867
6904
  await structure.changed();
6868
6905
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hollware/server",
3
- "version": "0.11.0",
3
+ "version": "1.0.0-rc.1",
4
4
  "description": "The Hollware Worker: operation handlers, MCP, RPC, HTTP, auth and D1 migrations.",
5
5
  "homepage": "https://hollware.com",
6
6
  "bugs": {
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@cloudflare/workers-oauth-provider": "1.2.3",
33
- "@hollware/core": "0.11.0",
33
+ "@hollware/core": "1.0.0-rc.1",
34
34
  "@modelcontextprotocol/server": "2.3.1",
35
35
  "@simplewebauthn/server": "14.0.3",
36
36
  "drizzle-orm": "0.45.4",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "devDependencies": {
40
40
  "@cloudflare/vitest-plugin": "1.4.3",
41
- "@hollware/agents": "0.11.0",
41
+ "@hollware/agents": "1.0.0-rc.1",
42
42
  "@modelcontextprotocol/client": "2.3.1",
43
43
  "@types/node": "24.19.2",
44
44
  "drizzle-kit": "0.31.11",