anbaric-cloud-hosting 1.16.4 → 1.17.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anbaric-cloud-hosting",
3
- "version": "1.16.4",
3
+ "version": "1.17.0",
4
4
  "description": "Anbaric Cloud hosting service: Postgres-backed job persistence and queuing exposed over an HTTP API",
5
5
  "license": "MIT",
6
6
  "author": "chris@anbaric.ai",
@@ -18,17 +18,17 @@
18
18
  "@aws-sdk/client-s3": "^3.1110.0",
19
19
  "@aws-sdk/client-secrets-manager": "^3.700.0",
20
20
  "@aws-sdk/client-servicediscovery": "^3.1110.0",
21
- "anbaric-data-store": "^1.16.4",
22
- "anbaric-plugins": "^1.16.4",
23
- "anbaric-tsapi": "^1.16.4",
21
+ "anbaric-data-store": "^1.17.0",
22
+ "anbaric-plugins": "^1.17.0",
23
+ "anbaric-tsapi": "^1.17.0",
24
24
  "esbuild": "^0.28.2",
25
25
  "pg": "^8.16.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/node": "^26.2.0",
29
29
  "@types/pg": "^8.15.0",
30
- "anbaric-impl-cloud": "^1.16.4",
31
- "anbaric-state-machine": "^1.16.4",
30
+ "anbaric-impl-cloud": "^1.17.0",
31
+ "anbaric-state-machine": "^1.17.0",
32
32
  "tsx": "^4.20.0",
33
33
  "typescript": "^7.0.2"
34
34
  },
@@ -23,7 +23,6 @@ import {StateMachinesHandler} from "./handlers/StateMachinesHandler";
23
23
  import {PluginsHandler} from "./handlers/PluginsHandler";
24
24
  import {AuthenticationMiddleware} from "./middleware/AuthenticationMiddleware";
25
25
  import {SessionMiddleware} from "./middleware/SessionMiddleware";
26
- import {PageDirectory} from "../plugins/PageDirectory";
27
26
  import {LoadedPlugin} from "../plugins/Plugin";
28
27
  import {Request} from "./Request";
29
28
  import {Router} from "./Router";
@@ -64,41 +63,34 @@ class HostingServer {
64
63
 
65
64
  const publicRouter = new Router();
66
65
  publicRouter.registerRoot(pages);
67
- if (plugins.length > 0) {
68
- for (const segment of new PageDirectory(plugins).topLevelSegments) {
69
- publicRouter.register(segment, pages);
70
- }
71
- publicRouter.register("plugins", new PluginsHandler(plugins));
72
- }
73
66
  publicRouter.register("ping", ping);
74
- publicRouter.register("audit", pages);
75
- publicRouter.register("whoami", new WhoamiHandler());
76
- publicRouter.register("jobs", jobs);
77
- publicRouter.register("queue", queueHandler);
78
- publicRouter.register("consumers", consumers);
79
- publicRouter.register("state-machines", stateMachines);
80
- if (documents) publicRouter.register("documents", documents);
81
- if (secrets) publicRouter.register("secrets", secrets);
82
- if (audits) publicRouter.register("audits", audits);
67
+ if (plugins.length > 0) publicRouter.registerApi("plugins", new PluginsHandler(plugins));
68
+ publicRouter.registerApi("whoami", new WhoamiHandler());
69
+ publicRouter.registerApi("jobs", jobs);
70
+ publicRouter.registerApi("queue", queueHandler);
71
+ publicRouter.registerApi("consumers", consumers);
72
+ publicRouter.registerApi("state-machines", stateMachines);
73
+ if (documents) publicRouter.registerApi("documents", documents);
74
+ if (secrets) publicRouter.registerApi("secrets", secrets);
75
+ if (audits) publicRouter.registerApi("audits", audits);
83
76
  if (cliAuthorizer) {
84
77
  publicRouter.register("authorize-cli", new AuthorizeCliHandler(cliAuthorizer, pages, tenant));
85
- publicRouter.register("keys", new KeysHandler(cliAuthorizer));
86
- publicRouter.register("manage-keys", pages);
78
+ publicRouter.registerApi("keys", new KeysHandler(cliAuthorizer));
87
79
  }
88
80
  if (buildLayer) {
89
- publicRouter.register("apps", new AppsHandler(buildLayer));
90
- publicRouter.registerFallback(new AppProxyHandler(buildLayer));
81
+ publicRouter.registerApi("apps", new AppsHandler(buildLayer));
82
+ publicRouter.register("app", new AppProxyHandler(buildLayer));
91
83
  }
92
84
 
93
85
  const internalRouter = new Router();
94
86
  internalRouter.register("ping", ping);
95
- internalRouter.register("jobs", jobs);
96
- internalRouter.register("queue", queueHandler);
97
- internalRouter.register("consumers", consumers);
98
- internalRouter.register("state-machines", stateMachines);
99
- if (documents) internalRouter.register("documents", documents);
100
- if (secrets) internalRouter.register("secrets", secrets);
101
- if (audits) internalRouter.register("audits", audits);
87
+ internalRouter.registerApi("jobs", jobs);
88
+ internalRouter.registerApi("queue", queueHandler);
89
+ internalRouter.registerApi("consumers", consumers);
90
+ internalRouter.registerApi("state-machines", stateMachines);
91
+ if (documents) internalRouter.registerApi("documents", documents);
92
+ if (secrets) internalRouter.registerApi("secrets", secrets);
93
+ if (audits) internalRouter.registerApi("audits", audits);
102
94
 
103
95
  const openRequests = (request : Request) =>
104
96
  request.url.pathname === "/ping" ||
@@ -14,6 +14,8 @@ class Request {
14
14
  user? : User;
15
15
  tenant? : Tenant;
16
16
 
17
+ readonly api : boolean;
18
+
17
19
  private parsed : URL;
18
20
  private segments : Array<string>;
19
21
 
@@ -21,7 +23,11 @@ class Request {
21
23
  rayId : string = randomUUID()) {
22
24
  this.rayId = rayId;
23
25
  this.parsed = new URL(incoming.url ?? "/", "http://localhost");
24
- this.segments = this.parsed.pathname.split("/").filter(Boolean);
26
+ const raw = this.parsed.pathname.split("/").filter(Boolean);
27
+ // The API lives under /api/v2; strip that prefix so resource/id/subresource
28
+ // address the resource, and flag it so the router serves API handlers only there.
29
+ this.api = raw[0] === "api" && raw[1] === "v2";
30
+ this.segments = this.api ? raw.slice(2) : raw;
25
31
  }
26
32
 
27
33
  get method() : string {
@@ -1,17 +1,22 @@
1
1
  import {Request} from "./Request";
2
2
  import {RequestHandler} from "./RequestHandler";
3
3
 
4
- /* Deliberately hollow: other systems register a handler per top-level
5
- resource, one for the root, and one fallback for anything unregistered
6
- (the app proxy). All business logic lives in the handlers. */
4
+ /* Deliberately hollow: other systems register API handlers (served only under
5
+ /api/v2), site handlers (top-level pages/proxies), one root handler, and one
6
+ fallback. All business logic lives in the handlers. */
7
7
  class Router {
8
8
 
9
- private handlers = new Map<string, RequestHandler>();
9
+ private apiHandlers = new Map<string, RequestHandler>();
10
+ private siteHandlers = new Map<string, RequestHandler>();
10
11
  private rootHandler? : RequestHandler;
11
12
  private fallbackHandler? : RequestHandler;
12
13
 
14
+ registerApi(resource : string, handler : RequestHandler) : void {
15
+ this.apiHandlers.set(resource, handler);
16
+ }
17
+
13
18
  register(resource : string, handler : RequestHandler) : void {
14
- this.handlers.set(resource, handler);
19
+ this.siteHandlers.set(resource, handler);
15
20
  }
16
21
 
17
22
  registerRoot(handler : RequestHandler) : void {
@@ -23,12 +28,17 @@ class Router {
23
28
  }
24
29
 
25
30
  async route(request : Request) : Promise<void> {
31
+ if (request.api) {
32
+ const handler = request.resource === undefined ? undefined : this.apiHandlers.get(request.resource);
33
+ return handler ? handler.handle(request) : request.notFound();
34
+ }
35
+
26
36
  if (request.resource === undefined) {
27
37
  if (this.rootHandler) return this.rootHandler.handle(request);
28
38
  return request.notFound();
29
39
  }
30
40
 
31
- const handler = this.handlers.get(request.resource);
41
+ const handler = this.siteHandlers.get(request.resource);
32
42
  if (handler) return handler.handle(request);
33
43
  if (this.fallbackHandler) return this.fallbackHandler.handle(request);
34
44
 
@@ -10,22 +10,24 @@ const HOP_BY_HOP = new Set([
10
10
  "te", "trailer", "transfer-encoding", "upgrade",
11
11
  ]);
12
12
 
13
- /* The router's fallback: any unregistered top-level path naming a running app
14
- is reverse-proxied to it. The app is exposed at /<appName>; that prefix is
15
- stripped before forwarding (the app sees the sub-path) and surfaced to the
16
- app as X-Forwarded-Prefix so it can rebuild public URLs. Request and response
17
- headers pass through both ways - notably cookies, Set-Cookie and Location - so
18
- sessions and redirects work from app-served HTML. */
13
+ /* Serves /app/<name>: the named running app is reverse-proxied to. The
14
+ /app/<name> prefix is stripped before forwarding (the app sees the sub-path)
15
+ and surfaced as X-Forwarded-Prefix so the app can rebuild public URLs.
16
+ Request and response headers pass through both ways - notably cookies,
17
+ Set-Cookie and Location - so sessions and redirects work from app-served
18
+ HTML. */
19
19
  class AppProxyHandler implements RequestHandler {
20
20
 
21
21
  constructor(private buildLayer : BuildLayer) {}
22
22
 
23
23
  async handle(request : Request) : Promise<void> {
24
- const appName = request.resource!;
24
+ await this.buildLayer.ensureHydrated();
25
+ const appName = request.id;
26
+ if (!appName) return request.notFound();
25
27
  const app = this.buildLayer.status(appName);
26
28
  if (!app || app.status !== "running") return request.notFound();
27
29
 
28
- const appPath = request.url.pathname.slice(`/${appName}`.length) || "/";
30
+ const appPath = request.url.pathname.slice(`/app/${appName}`.length) || "/";
29
31
  const body = request.method === "GET" || request.method === "HEAD" ? undefined : await request.rawBody();
30
32
 
31
33
  await new Promise<void>((resolve, reject) => {
@@ -50,7 +52,7 @@ class AppProxyHandler implements RequestHandler {
50
52
  private forwardHeaders(request : Request, appName : string) : OutgoingHttpHeaders {
51
53
  const headers = this.passThrough(request.raw.headers);
52
54
  delete headers.host;
53
- headers["x-forwarded-prefix"] = `/${appName}`;
55
+ headers["x-forwarded-prefix"] = `/app/${appName}`;
54
56
  headers["x-forwarded-host"] = request.raw.headers.host;
55
57
  headers["x-forwarded-proto"] = "https";
56
58
  return headers;
@@ -18,7 +18,7 @@ class PluginsHandler implements RequestHandler {
18
18
  private manifest(request : Request) : void {
19
19
  request.reply(200, this.plugins.map(({ name, plugin }) => ({
20
20
  name,
21
- bundle: `/plugins/${name}.js`,
21
+ bundle: `/api/v2/plugins/${name}.js`,
22
22
  pages: plugin.pages,
23
23
  widgets: plugin.widgets.map(({ page, id, title, position, data }) =>
24
24
  ({ page, id, title, position, hasData: data !== undefined })),