@knpkv/hub-app 0.0.0-stage → 0.2.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -2
  3. package/dist/client.d.ts +67 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/client.js +17 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/hub-client.d.ts +52 -0
  8. package/dist/hub-client.d.ts.map +1 -0
  9. package/dist/hub-client.js +97 -0
  10. package/dist/hub-client.js.map +1 -0
  11. package/dist/index.d.ts +6 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +6 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/main.d.ts +4 -0
  16. package/dist/main.d.ts.map +1 -0
  17. package/dist/main.js +29 -0
  18. package/dist/main.js.map +1 -0
  19. package/dist/manifest.d.ts +267 -0
  20. package/dist/manifest.d.ts.map +1 -0
  21. package/dist/manifest.js +107 -0
  22. package/dist/manifest.js.map +1 -0
  23. package/dist/module.d.ts +38 -0
  24. package/dist/module.d.ts.map +1 -0
  25. package/dist/module.js +70 -0
  26. package/dist/module.js.map +1 -0
  27. package/dist/node.d.ts +50 -0
  28. package/dist/node.d.ts.map +1 -0
  29. package/dist/node.js +253 -0
  30. package/dist/node.js.map +1 -0
  31. package/dist/route.d.ts +91 -0
  32. package/dist/route.d.ts.map +1 -0
  33. package/dist/route.js +88 -0
  34. package/dist/route.js.map +1 -0
  35. package/dist/standalone-page.d.ts +3 -0
  36. package/dist/standalone-page.d.ts.map +1 -0
  37. package/dist/standalone-page.js +28 -0
  38. package/dist/standalone-page.js.map +1 -0
  39. package/dist/standalone-shell.js +27 -0
  40. package/dist/standalone.d.ts +17 -0
  41. package/dist/standalone.d.ts.map +1 -0
  42. package/dist/standalone.js +140 -0
  43. package/dist/standalone.js.map +1 -0
  44. package/dist/wire.d.ts +75 -0
  45. package/dist/wire.d.ts.map +1 -0
  46. package/dist/wire.js +67 -0
  47. package/dist/wire.js.map +1 -0
  48. package/package.json +62 -3
package/dist/node.js ADDED
@@ -0,0 +1,253 @@
1
+ /**
2
+ * The one runner every app uses. The same entry serves either way:
3
+ *
4
+ * - **attached** (`--socket <path>`): an owner-only unix socket the hub proxies to. The caller and grant
5
+ * come from the hub's `x-hub-*` headers, which only the socket listener trusts. Shell services go to
6
+ * `HUB_URL` with the token in `HUB_APP_TOKEN_FILE`.
7
+ * - **standalone** (`[serve] [--port <n>] [--no-open]`): loopback TCP with its own owner session and the
8
+ * minimal shell at `/`. The caller is this machine's owner; shell services that need the hub are absent.
9
+ *
10
+ * Fixed surface: `GET /app/manifest`, `/app/health`, `/app/needs-you`, `/app/badge`, `/app/assets/*`, and
11
+ * module routes under `/v1/modules/<id>/*`.
12
+ *
13
+ * @module
14
+ */
15
+ import { NodeHttpServer } from "@effect/platform-node";
16
+ import { openOrServe } from "@knpkv/browser-pairing/launch";
17
+ import * as OwnerSession from "@knpkv/browser-pairing/owner-session";
18
+ import { Context, Deferred, Effect, FileSystem, Layer, Option, Path, Result, Schema } from "effect";
19
+ import { Etag, HttpPlatform, HttpRouter, HttpServer, HttpServerRequest, HttpServerResponse, HttpStaticServer } from "effect/http";
20
+ import { createServer } from "node:http";
21
+ import { hostname, userInfo } from "node:os";
22
+ import { parseArgs } from "node:util";
23
+ import { attachedLayer, standaloneLayer } from "./hub-client.js";
24
+ import { AppManifest, AppStartError } from "./manifest.js";
25
+ import { ModuleListener, validateModuleRegistry } from "./module.js";
26
+ import { CurrentGrant, dispatchToModule, Grant, isModuleRoute, ModuleCaller, ModuleHandlers, ModuleRouteFailure } from "./route.js";
27
+ import { standaloneShellHtml } from "./standalone-page.js";
28
+ import { appPaths, hubHeaders } from "./wire.js";
29
+ export const RunMode = Schema.Union([
30
+ Schema.TaggedStruct("Attached", { socket: Schema.String, hubUrl: Schema.String, tokenFile: Schema.String }),
31
+ Schema.TaggedStruct("Standalone", {
32
+ // 0 picks a free port; the sign-in link names the one bound.
33
+ port: Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 65_535 })),
34
+ open: Schema.Boolean
35
+ })
36
+ ]);
37
+ /** Read the mode from argv and environment. `--socket` means attached; anything else is standalone. */
38
+ export const readRunMode = (manifest, argv, env) => {
39
+ const parsed = Result.try({
40
+ try: () => parseArgs({
41
+ args: [...argv],
42
+ allowPositionals: true,
43
+ options: {
44
+ socket: { type: "string" },
45
+ port: { type: "string" },
46
+ "no-open": { type: "boolean", default: false }
47
+ }
48
+ }),
49
+ catch: (cause) => new AppStartError({ reason: "invalid_arguments", cause })
50
+ });
51
+ if (Result.isFailure(parsed))
52
+ return Result.fail(parsed.failure);
53
+ const { positionals, values } = parsed.success;
54
+ if (positionals.some((positional) => positional !== "serve")) {
55
+ return Result.fail(new AppStartError({ reason: "invalid_arguments", cause: positionals }));
56
+ }
57
+ const decode = Schema.decodeUnknownResult(RunMode);
58
+ const decoded = values.socket !== undefined
59
+ ? decode({
60
+ _tag: "Attached",
61
+ socket: values.socket,
62
+ hubUrl: env.HUB_URL,
63
+ tokenFile: env.HUB_APP_TOKEN_FILE
64
+ })
65
+ : decode({
66
+ _tag: "Standalone",
67
+ port: values.port === undefined ? manifest.standalone.defaultPort : Number(values.port),
68
+ open: values["no-open"] !== true
69
+ });
70
+ return Result.mapError(decoded, (cause) => new AppStartError({ reason: "invalid_arguments", cause }));
71
+ };
72
+ /** The manifest served at `/app/manifest`: the app's own, with `client` read from its build. */
73
+ const servedManifest = Effect.fn("HubApp.servedManifest")(function* (app) {
74
+ const fileSystem = yield* FileSystem.FileSystem;
75
+ const path = yield* Path.Path;
76
+ const hasCss = yield* fileSystem.exists(path.join(app.clientDirectory, "client.css"));
77
+ const manifest = yield* Schema.decodeUnknownEffect(AppManifest)({
78
+ ...app.manifest,
79
+ client: { entry: "client.js", css: hasCss ? ["client.css"] : [] }
80
+ });
81
+ const registry = validateModuleRegistry([manifest.module]);
82
+ if (Result.isFailure(registry) || manifest.module.id !== manifest.id) {
83
+ return yield* new AppStartError({ reason: "invalid_manifest", cause: manifest.id });
84
+ }
85
+ return manifest;
86
+ }, Effect.mapError((cause) => cause._tag === "AppStartError" ? cause : new AppStartError({ reason: "invalid_manifest", cause })));
87
+ /** Build the handler layer once, at startup, and refuse routes this package didn't build. */
88
+ const mountModule = Effect.fn("HubApp.mountModule")(function* (manifest, handlers) {
89
+ const context = yield* Layer.build(handlers).pipe(Effect.mapError((cause) => new AppStartError({ reason: "layer", cause })));
90
+ const built = Context.get(context, ModuleHandlers);
91
+ const identities = new Set();
92
+ for (const route of built.routes) {
93
+ const identity = `${route.method} ${route.path}`;
94
+ if (!isModuleRoute(route) || identities.has(identity)) {
95
+ return yield* new AppStartError({ reason: "invalid_manifest", cause: identity });
96
+ }
97
+ identities.add(identity);
98
+ }
99
+ const mounted = { descriptor: manifest.module, routes: built.routes };
100
+ return { mounted, needsYou: built.needsYou };
101
+ });
102
+ const failureResponse = (failure) => HttpServerResponse.json({ code: failure.code }, { status: failure.status });
103
+ const notFound = HttpServerResponse.json({ code: "not_found" }, { status: 404 });
104
+ /** The caller the hub authenticated, from the socket listener's `x-hub-caller` header. */
105
+ const hubCaller = Effect.gen(function* () {
106
+ const request = yield* HttpServerRequest.HttpServerRequest;
107
+ const header = request.headers[hubHeaders.caller];
108
+ if (header === undefined)
109
+ return yield* new ModuleRouteFailure({ status: 403, code: "no_caller" });
110
+ return yield* Schema.decodeUnknownEffect(Schema.fromJsonString(ModuleCaller))(header).pipe(Effect.mapError(() => new ModuleRouteFailure({ status: 403, code: "invalid_caller" })));
111
+ });
112
+ const hubGrant = Effect.gen(function* () {
113
+ const request = yield* HttpServerRequest.HttpServerRequest;
114
+ const header = request.headers[hubHeaders.grant];
115
+ return header === undefined ? null : Option.getOrNull(Schema.decodeUnknownOption(Grant)(header));
116
+ });
117
+ /** The hub listener that admitted a proxied request, from `x-hub-listener`; the descriptor must declare it. */
118
+ const hubListener = Effect.gen(function* () {
119
+ const request = yield* HttpServerRequest.HttpServerRequest;
120
+ return yield* Schema.decodeUnknownEffect(ModuleListener)(request.headers[hubHeaders.listener]).pipe(Effect.mapError(() => new ModuleRouteFailure({ status: 403, code: "no_listener" })));
121
+ });
122
+ /** `/app/*` and module routes; the listener decides who the caller is. */
123
+ const appRoutes = (manifest, mounted, needsYou, badge, policy) => HttpRouter.use((router) => Effect.gen(function* () {
124
+ yield* router.add("GET", appPaths.manifest, HttpServerResponse.json(manifest));
125
+ yield* router.add("GET", appPaths.health, HttpServerResponse.json({ ok: true }));
126
+ if (policy.serveHubReads) {
127
+ yield* router.add("GET", appPaths.needsYou, needsYou === undefined
128
+ ? notFound
129
+ : needsYou.pipe(Effect.flatMap((result) => HttpServerResponse.json(result)), Effect.catch(() => HttpServerResponse.json({ code: "needs_you_failed" }, { status: 503 }))));
130
+ yield* router.add("GET", appPaths.badge, badge === undefined
131
+ ? HttpServerResponse.json(null)
132
+ : Effect.flatMap(badge, (value) => HttpServerResponse.json(value)));
133
+ }
134
+ yield* router.add("*", `${policy.routePrefix}/v1/modules/${manifest.id}/*`, Effect.gen(function* () {
135
+ const request = yield* HttpServerRequest.HttpServerRequest;
136
+ const grant = yield* hubGrant;
137
+ const listener = yield* policy.listener;
138
+ const response = yield* dispatchToModule(mounted, {
139
+ method: request.method,
140
+ url: new URL(request.url.slice(policy.routePrefix.length), "http://app.invalid"),
141
+ listener,
142
+ authorize: policy.authorize,
143
+ sameOrigin: policy.sameOrigin,
144
+ readJson: () => request.json.pipe(Effect.mapError(() => new ModuleRouteFailure({ status: 400, code: "invalid_body" })))
145
+ }).pipe(Effect.provideService(CurrentGrant, grant));
146
+ return response === null
147
+ ? yield* notFound
148
+ : yield* HttpServerResponse.json(response.body, { status: response.status });
149
+ }).pipe(Effect.catchTag("ModuleRouteFailure", failureResponse)));
150
+ }));
151
+ const assetRoutes = (clientDirectory) => HttpStaticServer.layer({ root: clientDirectory, prefix: appPaths.assets, cacheControl: "no-cache" });
152
+ /** Remove a stale socket from a previous run; refuse to touch anything that isn't one. */
153
+ const clearSocket = Effect.fn("HubApp.clearSocket")(function* (socket) {
154
+ const fileSystem = yield* FileSystem.FileSystem;
155
+ if (!(yield* fileSystem.exists(socket)))
156
+ return;
157
+ const info = yield* fileSystem.stat(socket);
158
+ if (info.type !== "Socket")
159
+ return yield* new AppStartError({ reason: "listen", cause: "not a socket" });
160
+ yield* fileSystem.remove(socket);
161
+ }, Effect.mapError((cause) => cause._tag === "AppStartError" ? cause : new AppStartError({ reason: "listen", cause })));
162
+ /** Attached mode alone: what the hub and its tests run. Needs no browser opener. */
163
+ export const serveAttached = Effect.fn("HubApp.serveAttached")(function* (app, mode) {
164
+ const fileSystem = yield* FileSystem.FileSystem;
165
+ const manifest = yield* servedManifest(app);
166
+ const services = yield* Layer.build(attachedLayer({ app: manifest.id, hubUrl: mode.hubUrl, tokenFile: mode.tokenFile, declared: manifest.services }));
167
+ const { mounted, needsYou } = yield* mountModule(manifest, app.handlers).pipe(Effect.provideContext(services));
168
+ const badge = app.badge?.pipe(Effect.provideContext(services));
169
+ yield* clearSocket(mode.socket);
170
+ const policy = {
171
+ routePrefix: "",
172
+ serveHubReads: true,
173
+ // The hub forwards only requests its own listener admitted, and checked same-origin first; it names that
174
+ // listener so the descriptor's own `listeners` still decide.
175
+ listener: hubListener,
176
+ authorize: hubCaller,
177
+ sameOrigin: Effect.void
178
+ };
179
+ return yield* Layer.launch(HttpRouter.serve(Layer.mergeAll(appRoutes(manifest, mounted, needsYou, badge, policy), assetRoutes(app.clientDirectory)), {
180
+ disableListenLog: true,
181
+ disableLogger: true
182
+ }).pipe(Layer.provide(NodeHttpServer.layerServer(createServer, { path: mode.socket })), Layer.provide(HttpPlatform.layer), Layer.provide(Etag.layer),
183
+ // The hub's headers are trusted on this socket, so only its owner may connect, whatever the umask.
184
+ Layer.tap(() => fileSystem.chmod(mode.socket, 0o600).pipe(Effect.mapError((cause) => new AppStartError({ reason: "listen", cause }))))));
185
+ });
186
+ const ownerCaller = (login) => ({
187
+ login,
188
+ sameNode: true,
189
+ provenance: "verified_local_listener"
190
+ });
191
+ const serveStandalone = Effect.fn("HubApp.serveStandalone")(function* (app, mode, launch) {
192
+ const fileSystem = yield* FileSystem.FileSystem;
193
+ const path = yield* Path.Path;
194
+ // This package's own built browser module, read when asked for: a missing build fails that request only.
195
+ const shellScript = path.fromFileUrl(new URL("./standalone-shell.js", import.meta.url)).pipe(Effect.flatMap((file) => fileSystem.readFileString(file)), Effect.map((script) => HttpServerResponse.text(script, { contentType: "text/javascript; charset=utf-8" })), Effect.catchTag("PlatformError", () => HttpServerResponse.json({ code: "shell_missing" }, { status: 500 })));
196
+ const manifest = yield* servedManifest(app);
197
+ const host = hostname();
198
+ const services = yield* Layer.build(standaloneLayer({ host, isHub: false, hosts: [host] }));
199
+ const { mounted, needsYou } = yield* mountModule(manifest, app.handlers).pipe(Effect.provideContext(services));
200
+ const badge = app.badge?.pipe(Effect.provideContext(services));
201
+ const login = userInfo().username;
202
+ const page = standaloneShellHtml(manifest);
203
+ const shell = HttpRouter.use((router) => Effect.gen(function* () {
204
+ yield* router.add("GET", "/", HttpServerResponse.html(page));
205
+ yield* router.add("GET", "/app/shell.js", shellScript);
206
+ }));
207
+ /** The owner session is made once the port is bound, so `--port 0` gets a real origin and cookie. */
208
+ const server = (ready) => Layer.unwrap(Effect.gen(function* () {
209
+ const bound = yield* HttpServer.HttpServer;
210
+ if (bound.address._tag === "UnixPathAddress") {
211
+ return yield* new AppStartError({ reason: "listen", cause: "standalone needs a TCP listener" });
212
+ }
213
+ const origin = OwnerSession.loopbackOrigin("127.0.0.1", bound.address.port);
214
+ const session = yield* OwnerSession.make({
215
+ product: manifest.package,
216
+ cookieName: `hub_app_${manifest.id.replaceAll("-", "_")}_${bound.address.port}`,
217
+ writes: "csrf",
218
+ freshUrlHint: `restart ${manifest.package} for a fresh URL`,
219
+ authorityOrigin: origin,
220
+ browserOrigin: origin
221
+ });
222
+ // The owner's session cookie is the only credential here; `x-hub-*` headers are never read.
223
+ const authorizeOwner = Effect.gen(function* () {
224
+ const request = yield* HttpServerRequest.HttpServerRequest;
225
+ yield* session.authorizeHttp(request.cookies[session.cookieName] ?? "").pipe(Effect.catchTags({
226
+ OwnerSessionUnauthorizedError: () => Effect.fail(new ModuleRouteFailure({ status: 403, code: "unauthorized" })),
227
+ OwnerSessionForbiddenError: () => Effect.fail(new ModuleRouteFailure({ status: 403, code: "forbidden" }))
228
+ }));
229
+ return ownerCaller(login);
230
+ });
231
+ const policy = {
232
+ // The owner session's cookie is scoped to `/api`, so module routes live there.
233
+ routePrefix: "/api",
234
+ serveHubReads: false,
235
+ listener: Effect.succeed("local"),
236
+ authorize: authorizeOwner,
237
+ // authorizeHttp checks Origin, Fetch Metadata and the CSRF token for unsafe methods.
238
+ sameOrigin: Effect.void
239
+ };
240
+ return HttpRouter.serve(Layer.mergeAll(appRoutes(manifest, mounted, needsYou, badge, policy), assetRoutes(app.clientDirectory), OwnerSession.BootstrapRouter, shell), { disableListenLog: true, disableLogger: true }).pipe(Layer.provide(Layer.succeed(OwnerSession.OwnerSession, session)), Layer.tap(() => session.mintBootstrapCode.pipe(Effect.flatMap((code) => Deferred.succeed(ready, OwnerSession.bootstrapUrl(origin, code))))));
241
+ })).pipe(Layer.provide(NodeHttpServer.layerServer(createServer, { host: "127.0.0.1", port: mode.port })), Layer.provide(HttpPlatform.layer), Layer.provide(Etag.layer));
242
+ const serve = (onReady) => OwnerSession.serveWithBootstrapUrl(server, onReady);
243
+ return yield* openOrServe({ login: Effect.succeedNone, serve, launch });
244
+ });
245
+ /**
246
+ * Serve the app in `mode` until interrupted. `launch` says whether standalone mode opens a browser for its
247
+ * sign-in link; the executable decides it from its terminal and environment.
248
+ */
249
+ export const serveApp = Effect.fn("HubApp.serveApp")(function* (app, mode, launch) {
250
+ return mode._tag === "Attached" ? yield* serveAttached(app, mode) : yield* serveStandalone(app, mode, launch);
251
+ }, Effect.mapError((cause) => cause._tag === "AppStartError" ? cause : new AppStartError({ reason: "listen", cause })));
252
+ export { runApp } from "./main.js";
253
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAEtD,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAC3D,OAAO,KAAK,YAAY,MAAM,sCAAsC,CAAA;AACpE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACnG,OAAO,EACL,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAErC,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAChE,OAAO,EAAE,WAAW,EAAyB,aAAa,EAAE,MAAM,eAAe,CAAA;AACjF,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACpE,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,KAAK,EACL,aAAa,EACb,YAAY,EACZ,cAAc,EACd,kBAAkB,EAEnB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAWhD,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC;IAClC,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IAC3G,MAAM,CAAC,YAAY,CAAC,YAAY,EAAE;QAChC,6DAA6D;QAC7D,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QACzE,IAAI,EAAE,MAAM,CAAC,OAAO;KACrB,CAAC;CACH,CAAC,CAAA;AAGF,uGAAuG;AACvG,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,QAA0B,EAC1B,IAA2B,EAC3B,GAAiD,EACV,EAAE;IACzC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC;QACxB,GAAG,EAAE,GAAG,EAAE,CACR,SAAS,CAAC;YACR,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;YACf,gBAAgB,EAAE,IAAI;YACtB,OAAO,EAAE;gBACP,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC1B,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACxB,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE;aAC/C;SACF,CAAC;QACJ,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,aAAa,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE,KAAK,EAAE,CAAC;KAC5E,CAAC,CAAA;IACF,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAChE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAA;IAC9C,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,KAAK,OAAO,CAAC,EAAE,CAAC;QAC7D,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,aAAa,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,CAAA;IAC5F,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAA;IAClD,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,KAAK,SAAS;QACzC,CAAC,CAAC,MAAM,CAAC;YACP,IAAI,EAAE,UAAU;YAChB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM,EAAE,GAAG,CAAC,OAAO;YACnB,SAAS,EAAE,GAAG,CAAC,kBAAkB;SAClC,CAAC;QACF,CAAC,CAAC,MAAM,CAAC;YACP,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;YACvF,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,KAAK,IAAI;SACjC,CAAC,CAAA;IACJ,OAAO,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,aAAa,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;AACvG,CAAC,CAAA;AAED,gGAAgG;AAChG,MAAM,cAAc,GAAG,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,CACvD,QAAQ,CAAC,EAAC,GAAgC;IACxC,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAA;IAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAA;IAC7B,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC,CAAA;IACrF,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC;QAC9D,GAAG,GAAG,CAAC,QAAQ;QACf,MAAM,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE;KAClE,CAAC,CAAA;IACF,MAAM,QAAQ,GAAG,sBAAsB,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAA;IAC1D,IAAI,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,QAAQ,CAAC,EAAE,EAAE,CAAC;QACrE,OAAO,KAAK,CAAC,CAAC,IAAI,aAAa,CAAC,EAAE,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAA;IACrF,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC,EACD,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CACxB,KAAK,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,EAAE,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,CAClG,CACF,CAAA;AAED,6FAA6F;AAC7F,MAAM,WAAW,GAAG,MAAM,CAAC,EAAE,CAAC,oBAAoB,CAAC,CAAC,QAAQ,CAAC,EAC3D,QAAqB,EACrB,QAA2D;IAE3D,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAC/C,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,aAAa,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAC1E,CAAA;IACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,CAAA;IAClD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAA;IACpC,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAA;QAChD,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACtD,OAAO,KAAK,CAAC,CAAC,IAAI,aAAa,CAAC,EAAE,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAA;QAClF,CAAC;QACD,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC1B,CAAC;IACD,MAAM,OAAO,GAAkB,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAA;IACpF,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAA;AAC9C,CAAC,CAAC,CAAA;AAEF,MAAM,eAAe,GAAG,CAAC,OAA2B,EAAE,EAAE,CACtD,kBAAkB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;AAE7E,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;AAEhF,0FAA0F;AAC1F,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACpC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,iBAAiB,CAAC,iBAAiB,CAAA;IAC1D,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;IACjD,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC,CAAC,IAAI,kBAAkB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAA;IAClG,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CACxF,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,kBAAkB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC,CACvF,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,iBAAiB,CAAC,iBAAiB,CAAA;IAC1D,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;IAChD,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;AAClG,CAAC,CAAC,CAAA;AAEF,+GAA+G;AAC/G,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACtC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,iBAAiB,CAAC,iBAAiB,CAAA;IAC1D,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CACjG,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,kBAAkB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC,CACpF,CAAA;AACH,CAAC,CAAC,CAAA;AAYF,0EAA0E;AAC1E,MAAM,SAAS,GAAG,CAChB,QAAqB,EACrB,OAAsB,EACtB,QAAqD,EACrD,KAA0F,EAC1F,MAAkE,EAClE,EAAE,CACF,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CACxB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC9E,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAChF,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;QACzB,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CACf,KAAK,EACL,QAAQ,CAAC,QAAQ,EACjB,QAAQ,KAAK,SAAS;YACpB,CAAC,CAAC,QAAQ;YACV,CAAC,CAAC,QAAQ,CAAC,IAAI,CACb,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAC3D,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAC3F,CACJ,CAAA;QACD,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CACf,KAAK,EACL,QAAQ,CAAC,KAAK,EACd,KAAK,KAAK,SAAS;YACjB,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;YAC/B,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CACrE,CAAA;IACH,CAAC;IACD,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CACf,GAAG,EACH,GAAG,MAAM,CAAC,WAAW,eAAe,QAAQ,CAAC,EAAE,IAAI,EACnD,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,iBAAiB,CAAC,iBAAiB,CAAA;QAC1D,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAA;QAC7B,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAA;QACvC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,gBAAgB,CAAC,OAAO,EAAE;YAChD,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,GAAG,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,oBAAoB,CAAC;YAChF,QAAQ;YACR,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,QAAQ,EAAE,GAAG,EAAE,CACb,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,kBAAkB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;SAC1G,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,CAAA;QACnD,OAAO,QAAQ,KAAK,IAAI;YACtB,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ;YACjB,CAAC,CAAC,KAAK,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;IAChF,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,EAAE,eAAe,CAAC,CAAC,CAChE,CAAA;AACH,CAAC,CAAC,CACH,CAAA;AAEH,MAAM,WAAW,GAAG,CAAC,eAAuB,EAAE,EAAE,CAC9C,gBAAgB,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC,CAAA;AAEtG,0FAA0F;AAC1F,MAAM,WAAW,GAAG,MAAM,CAAC,EAAE,CAAC,oBAAoB,CAAC,CAAC,QAAQ,CAAC,EAAC,MAAc;IAC1E,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAA;IAC/C,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAAE,OAAM;IAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC3C,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC,CAAC,IAAI,aAAa,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAA;IACxG,KAAK,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;AAClC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;AAEvH,oFAAoF;AACpF,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,EAAE,CAAC,sBAAsB,CAAC,CAAC,QAAQ,CAAC,EACtE,GAAoB,EACpB,IAAqD;IAErD,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAA;IAC/C,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,CAAA;IAC3C,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CACjC,aAAa,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC,CACjH,CAAA;IACD,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC,CAAC,WAAW,CAAC,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC9G,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC9D,KAAK,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC/B,MAAM,MAAM,GAA+D;QACzE,WAAW,EAAE,EAAE;QACf,aAAa,EAAE,IAAI;QACnB,yGAAyG;QACzG,6DAA6D;QAC7D,QAAQ,EAAE,WAAW;QACrB,SAAS,EAAE,SAAS;QACpB,UAAU,EAAE,MAAM,CAAC,IAAI;KACxB,CAAA;IACD,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,CACxB,UAAU,CAAC,KAAK,CACd,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,WAAW,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,EACvG;QACE,gBAAgB,EAAE,IAAI;QACtB,aAAa,EAAE,IAAI;KACpB,CACF,CAAC,IAAI,CACJ,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,EAC9E,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,EACjC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;IACzB,mGAAmG;IACnG,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CACb,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,IAAI,CACvC,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,aAAa,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAC3E,CACF,CACF,CACF,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,MAAM,WAAW,GAAG,CAAC,KAAa,EAA+B,EAAE,CAAC,CAAC;IACnE,KAAK;IACL,QAAQ,EAAE,IAAI;IACd,UAAU,EAAE,yBAAyB;CACtC,CAAC,CAAA;AAEF,MAAM,eAAe,GAAG,MAAM,CAAC,EAAE,CAAC,wBAAwB,CAAC,CAAC,QAAQ,CAAC,EACnE,GAAoB,EACpB,IAAuD,EACvD,MAAqB;IAErB,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAA;IAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAA;IAC7B,yGAAyG;IACzG,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,uBAAuB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAC1F,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,EACzD,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,gCAAgC,EAAE,CAAC,CAAC,EAC1G,MAAM,CAAC,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAC5G,CAAA;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,CAAA;IAC3C,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAA;IACvB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;IAC3F,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC,CAAC,WAAW,CAAC,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC9G,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC9D,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC,QAAQ,CAAA;IACjC,MAAM,IAAI,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAA;IAC1C,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CACtC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;QAC5D,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,eAAe,EAAE,WAAW,CAAC,CAAA;IACxD,CAAC,CAAC,CACH,CAAA;IACD,qGAAqG;IACrG,MAAM,MAAM,GAAG,CAAC,KAAgC,EAAE,EAAE,CAClD,KAAK,CAAC,MAAM,CACV,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAA;QAC1C,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;YAC7C,OAAO,KAAK,CAAC,CAAC,IAAI,aAAa,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC,CAAA;QACjG,CAAC;QACD,MAAM,MAAM,GAAG,YAAY,CAAC,cAAc,CAAC,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAC3E,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC;YACvC,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,UAAU,EAAE,WAAW,QAAQ,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE;YAC/E,MAAM,EAAE,MAAM;YACd,YAAY,EAAE,WAAW,QAAQ,CAAC,OAAO,kBAAkB;YAC3D,eAAe,EAAE,MAAM;YACvB,aAAa,EAAE,MAAM;SACtB,CAAC,CAAA;QACF,4FAA4F;QAC5F,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YACzC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,iBAAiB,CAAC,iBAAiB,CAAA;YAC1D,KAAK,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAC1E,MAAM,CAAC,SAAS,CAAC;gBACf,6BAA6B,EAAE,GAAG,EAAE,CAClC,MAAM,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;gBAC5E,0BAA0B,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;aAC1G,CAAC,CACH,CAAA;YACD,OAAO,WAAW,CAAC,KAAK,CAAC,CAAA;QAC3B,CAAC,CAAC,CAAA;QACF,MAAM,MAAM,GAA+D;YACzE,+EAA+E;YAC/E,WAAW,EAAE,MAAM;YACnB,aAAa,EAAE,KAAK;YACpB,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;YACjC,SAAS,EAAE,cAAc;YACzB,qFAAqF;YACrF,UAAU,EAAE,MAAM,CAAC,IAAI;SACxB,CAAA;QACD,OAAO,UAAU,CAAC,KAAK,CACrB,KAAK,CAAC,QAAQ,CACZ,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,EACrD,WAAW,CAAC,GAAG,CAAC,eAAe,CAAC,EAChC,YAAY,CAAC,eAAe,EAC5B,KAAK,CACN,EACD,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAChD,CAAC,IAAI,CACJ,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,EAChE,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CACb,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAC5B,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,YAAY,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAC3F,CACF,CACF,CAAA;IACH,CAAC,CAAC,CACH,CAAC,IAAI,CACJ,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,EAC/F,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,EACjC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAC1B,CAAA;IACH,MAAM,KAAK,GAAG,CAAC,OAAuE,EAAE,EAAE,CACxF,YAAY,CAAC,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACrD,OAAO,KAAK,CAAC,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;AACzE,CAAC,CAAC,CAAA;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,EAC5D,GAAoB,EACpB,IAAa,EACb,MAAqB;IAErB,OAAO,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,eAAe,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAA;AAC/G,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;AAEvH,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Module API routes: schema-checked handlers under `/v1/modules/<module id>`, shared by the hub's core
3
+ * modules and every app server. Moved from herdr-hub's module-server.
4
+ *
5
+ * **Mental model**
6
+ *
7
+ * - A route is a frozen `{ method, path }` identity; its handler stays in a private table, so a spread copy
8
+ * can't swap the decoding out. Build routes with {@link moduleReadRoute} and {@link moduleWriteRoute}.
9
+ * - The caller comes from whoever authenticated the request (the hub's listener, or a standalone app's
10
+ * owner session), never from the client.
11
+ * - A grant is the hub's handle for the request it proxied. Shell services that act for the caller read it
12
+ * from {@link CurrentGrant}; handlers never see or forward a caller themselves.
13
+ *
14
+ * @module
15
+ */
16
+ import { Context, Effect, Schema } from "effect";
17
+ import type { ModuleDescriptor, ModuleListener } from "./module.js";
18
+ /**
19
+ * Authenticated login from the listener authorization result, never client-supplied selectors.
20
+ * `sameNode` is true only when the server proved the caller is the hub's own tailnet node; an
21
+ * authorizer that does not say decodes as false. `provenance` is `verified_local_listener` only for a
22
+ * direct, unproxied loopback request; an authorizer that does not say decodes as remote.
23
+ */
24
+ export declare const ModuleCaller: Schema.Struct<{
25
+ readonly login: Schema.String;
26
+ readonly sameNode: Schema.withDecodingDefaultKey<Schema.Boolean, never>;
27
+ readonly provenance: Schema.withDecodingDefaultKey<Schema.Literals<readonly ["verified_local_listener", "authenticated_remote"]>, never>;
28
+ }>;
29
+ export type ModuleCaller = typeof ModuleCaller.Type;
30
+ export interface ModuleResponse {
31
+ readonly status: 200 | 201 | 202;
32
+ readonly body: Schema.Json;
33
+ }
34
+ declare const ModuleRouteFailure_base: Schema.Class<ModuleRouteFailure, Schema.TaggedStruct<"ModuleRouteFailure", {
35
+ readonly status: Schema.Literals<readonly [400, 403, 404, 409, 500, 503]>;
36
+ readonly code: Schema.String;
37
+ }>, import("effect/Cause").YieldableError>;
38
+ /** Safe route failures carry public copy only. Provider errors stay inside the module. */
39
+ export declare class ModuleRouteFailure extends ModuleRouteFailure_base {
40
+ }
41
+ /** The hub's opaque handle for one proxied request; null outside a hub-proxied request. */
42
+ export declare const Grant: Schema.brand<Schema.String, "HubGrant">;
43
+ export type Grant = typeof Grant.Type;
44
+ /** Set by the app runner for each request the hub proxied with an `x-hub-grant` header. */
45
+ export declare const CurrentGrant: Context.Reference<(string & import("effect/Brand").Brand<"HubGrant">) | null>;
46
+ declare const ModuleRouteTypeId: unique symbol;
47
+ /**
48
+ * Construct routes with the schema-backed factories. `invalid_route` at registration means an
49
+ * unrecognized route identity, including one built by a second copy of this package.
50
+ */
51
+ export interface ModuleApiRoute {
52
+ readonly [ModuleRouteTypeId]: true;
53
+ readonly method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
54
+ /** Relative to `/v1/modules/<module id>`. */
55
+ readonly path: `/${string}`;
56
+ }
57
+ /** True for a route built by this package instance with a well-formed path. */
58
+ export declare const isModuleRoute: (route: ModuleApiRoute) => boolean;
59
+ /** Run a registered route with an already-authorized caller. */
60
+ export declare const invokeModuleRoute: (route: ModuleApiRoute, query: URLSearchParams, body: Schema.Json, caller: ModuleCaller) => Effect.Effect<ModuleResponse, ModuleRouteFailure>;
61
+ declare const ModuleHandlers_base: Context.ServiceClass<ModuleHandlers, "@knpkv/hub-app/ModuleHandlers", {
62
+ readonly routes: ReadonlyArray<ModuleApiRoute>;
63
+ /** Expected source failures reject; the hub's collector records them and keeps the other providers. */
64
+ readonly needsYou?: Effect.Effect<unknown, unknown>;
65
+ }>;
66
+ export declare class ModuleHandlers extends ModuleHandlers_base {
67
+ }
68
+ /** Read routes decode a query codec and encode success; stable services are acquired by the product layer. */
69
+ export declare const moduleReadRoute: <Q, A>(path: ModuleApiRoute["path"], querySchema: Schema.Codec<Q, URLSearchParams, never, never>, success: Schema.Codec<A, Schema.Json, never, never>, handle: (query: Q, caller: ModuleCaller) => Effect.Effect<A, ModuleRouteFailure>, status?: ModuleResponse["status"]) => ModuleApiRoute;
70
+ /** Decode query and body after authorization; handlers receive the validated selectors and body. */
71
+ export declare const moduleWriteRoute: <Q, A, B>(method: Exclude<ModuleApiRoute["method"], "GET">, path: ModuleApiRoute["path"], querySchema: Schema.Codec<Q, URLSearchParams, never, never>, schema: Schema.Codec<A, unknown, never, never>, success: Schema.Codec<B, Schema.Json, never, never>, handle: (body: A, query: Q, caller: ModuleCaller) => Effect.Effect<B, ModuleRouteFailure>, status?: ModuleResponse["status"]) => ModuleApiRoute;
72
+ /** One module as a listener serves it: its metadata and the routes its handler layer built. */
73
+ export interface MountedModule {
74
+ readonly descriptor: ModuleDescriptor;
75
+ readonly routes: ReadonlyArray<ModuleApiRoute>;
76
+ }
77
+ /**
78
+ * Serve one request against one module. Null when the path is outside the module, the listener isn't
79
+ * declared, or no route matches; callers answer 404 on null for `/v1/modules/*`. Authorization runs
80
+ * only for a matched route; same-origin and the body only for mutations.
81
+ */
82
+ export declare const dispatchToModule: <E, R, OriginError = E, BodyError = E>(module: MountedModule, request: {
83
+ readonly method: string;
84
+ readonly url: URL;
85
+ readonly listener: ModuleListener | "work";
86
+ readonly authorize: Effect.Effect<typeof ModuleCaller.Encoded, E, R>;
87
+ readonly sameOrigin: Effect.Effect<unknown, OriginError, R>;
88
+ readonly readJson: () => Effect.Effect<Schema.Json, BodyError, R>;
89
+ }) => Effect.Effect<ModuleResponse | null, BodyError | E | OriginError | ModuleRouteFailure, R>;
90
+ export {};
91
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../src/route.ts"],"names":[],"mappings":"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"}
package/dist/route.js ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Module API routes: schema-checked handlers under `/v1/modules/<module id>`, shared by the hub's core
3
+ * modules and every app server. Moved from herdr-hub's module-server.
4
+ *
5
+ * **Mental model**
6
+ *
7
+ * - A route is a frozen `{ method, path }` identity; its handler stays in a private table, so a spread copy
8
+ * can't swap the decoding out. Build routes with {@link moduleReadRoute} and {@link moduleWriteRoute}.
9
+ * - The caller comes from whoever authenticated the request (the hub's listener, or a standalone app's
10
+ * owner session), never from the client.
11
+ * - A grant is the hub's handle for the request it proxied. Shell services that act for the caller read it
12
+ * from {@link CurrentGrant}; handlers never see or forward a caller themselves.
13
+ *
14
+ * @module
15
+ */
16
+ import { Context, Effect, Schema } from "effect";
17
+ /**
18
+ * Authenticated login from the listener authorization result, never client-supplied selectors.
19
+ * `sameNode` is true only when the server proved the caller is the hub's own tailnet node; an
20
+ * authorizer that does not say decodes as false. `provenance` is `verified_local_listener` only for a
21
+ * direct, unproxied loopback request; an authorizer that does not say decodes as remote.
22
+ */
23
+ export const ModuleCaller = Schema.Struct({
24
+ login: Schema.String.check(Schema.isNonEmpty(), Schema.isMaxLength(254), Schema.isPattern(/^[^\s\p{Cc}]+$/u)),
25
+ sameNode: Schema.Boolean.pipe(Schema.withDecodingDefaultKey(Effect.succeed(false))),
26
+ provenance: Schema.Literals(["verified_local_listener", "authenticated_remote"]).pipe(Schema.withDecodingDefaultKey(Effect.succeed("authenticated_remote")))
27
+ });
28
+ /** Safe route failures carry public copy only. Provider errors stay inside the module. */
29
+ export class ModuleRouteFailure extends Schema.TaggedError()("ModuleRouteFailure", {
30
+ status: Schema.Literals([400, 403, 404, 409, 500, 503]),
31
+ code: Schema.String
32
+ }) {
33
+ }
34
+ /** The hub's opaque handle for one proxied request; null outside a hub-proxied request. */
35
+ export const Grant = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{32,128}$/)).pipe(Schema.brand("HubGrant"));
36
+ /** Set by the app runner for each request the hub proxied with an `x-hub-grant` header. */
37
+ export const CurrentGrant = Context.Reference("@knpkv/hub-app/CurrentGrant", {
38
+ defaultValue: () => null
39
+ });
40
+ const ModuleRouteTypeId = Symbol("ModuleApiRoute");
41
+ const ApiPath = Schema.String.check(Schema.isPattern(/^(\/[a-z0-9][a-z0-9-]*)+$/));
42
+ const routeHandlers = new WeakMap();
43
+ /** Keep the executable handler private; spread copies cannot replace schema decoding. */
44
+ const registerRoute = (method, path, handle) => {
45
+ const route = Object.freeze({ [ModuleRouteTypeId]: true, method, path });
46
+ routeHandlers.set(route, handle);
47
+ return route;
48
+ };
49
+ /** True for a route built by this package instance with a well-formed path. */
50
+ export const isModuleRoute = (route) => routeHandlers.has(route) && Schema.is(ApiPath)(route.path);
51
+ /** Run a registered route with an already-authorized caller. */
52
+ export const invokeModuleRoute = (route, query, body, caller) => {
53
+ const handle = routeHandlers.get(route);
54
+ return handle === undefined
55
+ ? Effect.fail(new ModuleRouteFailure({ status: 500, code: "invalid_route" }))
56
+ : handle(query, body, caller);
57
+ };
58
+ export class ModuleHandlers extends Context.Service()("@knpkv/hub-app/ModuleHandlers") {
59
+ }
60
+ /** Decode query selectors before the handler; unknown keys fail instead of being discarded. */
61
+ const decodeModuleQuery = (schema, query) => Schema.decodeUnknownEffect(schema, { onExcessProperty: "error" })(query).pipe(Effect.mapError(() => new ModuleRouteFailure({ status: 400, code: "invalid_query" })));
62
+ /** Read routes decode a query codec and encode success; stable services are acquired by the product layer. */
63
+ export const moduleReadRoute = (path, querySchema, success, handle, status = 200) => registerRoute("GET", path, (query, _body, caller) => decodeModuleQuery(querySchema, query).pipe(Effect.flatMap((decoded) => handle(decoded, caller)), Effect.flatMap((body) => Schema.encodeEffect(success)(body)), Effect.mapError((error) => error._tag === "ModuleRouteFailure" ? error : new ModuleRouteFailure({ status: 500, code: "invalid_response" })), Effect.map((body) => ({ status, body }))));
64
+ /** Decode query and body after authorization; handlers receive the validated selectors and body. */
65
+ export const moduleWriteRoute = (method, path, querySchema, schema, success, handle, status = 200) => registerRoute(method, path, (query, body, caller) => decodeModuleQuery(querySchema, query).pipe(Effect.flatMap((decodedQuery) => Schema.decodeUnknownEffect(schema)(body).pipe(Effect.mapError(() => new ModuleRouteFailure({ status: 400, code: "invalid_body" })), Effect.flatMap((decoded) => handle(decoded, decodedQuery, caller)))), Effect.flatMap((response) => Schema.encodeEffect(success)(response).pipe(Effect.mapError(() => new ModuleRouteFailure({ status: 500, code: "invalid_response" })))), Effect.map((body) => ({ status, body }))));
66
+ /**
67
+ * Serve one request against one module. Null when the path is outside the module, the listener isn't
68
+ * declared, or no route matches; callers answer 404 on null for `/v1/modules/*`. Authorization runs
69
+ * only for a matched route; same-origin and the body only for mutations.
70
+ */
71
+ export const dispatchToModule = Effect.fn("ModuleRoute.dispatch")(function* (module, request) {
72
+ const prefix = `/v1/modules/${module.descriptor.id}`;
73
+ if (!request.url.pathname.startsWith(`${prefix}/`))
74
+ return null;
75
+ if (!module.descriptor.listeners.some((listener) => listener === request.listener))
76
+ return null;
77
+ const route = module.routes.find((candidate) => candidate.method === request.method && `${prefix}${candidate.path}` === request.url.pathname);
78
+ if (route === undefined)
79
+ return null;
80
+ const authorizedCaller = yield* request.authorize;
81
+ const caller = yield* Schema.decodeUnknownEffect(ModuleCaller)(authorizedCaller).pipe(Effect.mapError(() => new ModuleRouteFailure({ status: 500, code: "invalid_caller" })));
82
+ const mutation = route.method !== "GET";
83
+ if (mutation)
84
+ yield* request.sameOrigin;
85
+ const body = mutation ? yield* request.readJson() : null;
86
+ return yield* invokeModuleRoute(route, request.url.searchParams, body, caller);
87
+ });
88
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.js","sourceRoot":"","sources":["../src/route.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { AppManifest } from "./manifest.js";
2
+ export declare const standaloneShellHtml: (manifest: AppManifest) => string;
3
+ //# sourceMappingURL=standalone-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standalone-page.d.ts","sourceRoot":"","sources":["../src/standalone-page.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAU3C,eAAO,MAAM,mBAAmB,aAAc,WAAW,KAAG,MAoB9C,CAAA"}
@@ -0,0 +1,28 @@
1
+ /** The standalone shell's HTML: the manifest inline, the app's stylesheets, and the shell module. @module */
2
+ import { Schema } from "effect";
3
+ import { AppManifest } from "./manifest.js";
4
+ const escapeHtml = (text) => text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
5
+ const encodeManifest = Schema.encodeSync(Schema.fromJsonString(AppManifest));
6
+ /** The manifest as JSON inside a script element: `<` can't close the element when written as an escape. */
7
+ const inlineManifest = (manifest) => encodeManifest(manifest).replaceAll("<", "\\u003c");
8
+ export const standaloneShellHtml = (manifest) => [
9
+ "<!doctype html>",
10
+ "<html lang=\"en\">",
11
+ "<head>",
12
+ "<meta charset=\"utf-8\">",
13
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">",
14
+ `<title>${escapeHtml(manifest.module.label)}</title>`,
15
+ ...manifest.client.css.map((file) => `<link rel="stylesheet" href="/app/assets/${escapeHtml(file)}">`),
16
+ `<script type="application/json" id="hub-app-manifest">${inlineManifest(manifest)}</script>`,
17
+ "<script type=\"module\" src=\"/app/shell.js\"></script>",
18
+ "</head>",
19
+ "<body>",
20
+ "<header class=\"hub-app-standalone-header\"><span data-hub-shell=\"label\"></span>",
21
+ "<nav aria-label=\"Pages\" data-hub-shell=\"pages\"></nav><span data-hub-shell=\"crumb\"></span>",
22
+ "<span data-hub-shell=\"slot\"></span></header>",
23
+ `<main id="hub-app-root" data-app="${escapeHtml(manifest.id)}"></main>`,
24
+ "<div id=\"hub-app-portals\"></div>",
25
+ "</body>",
26
+ "</html>"
27
+ ].join("\n");
28
+ //# sourceMappingURL=standalone-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standalone-page.js","sourceRoot":"","sources":["../src/standalone-page.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAC7G,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE3C,MAAM,UAAU,GAAG,CAAC,IAAY,EAAU,EAAE,CAC1C,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;AAE1G,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC,CAAA;AAE5E,2GAA2G;AAC3G,MAAM,cAAc,GAAG,CAAC,QAAqB,EAAU,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC,CAAA;AAE7G,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,QAAqB,EAAU,EAAE,CACnE;IACE,iBAAiB;IACjB,oBAAoB;IACpB,QAAQ;IACR,0BAA0B;IAC1B,0EAA0E;IAC1E,UAAU,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU;IACrD,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,4CAA4C,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;IACtG,yDAAyD,cAAc,CAAC,QAAQ,CAAC,WAAW;IAC5F,yDAAyD;IACzD,SAAS;IACT,QAAQ;IACR,oFAAoF;IACpF,iGAAiG;IACjG,gDAAgD;IAChD,qCAAqC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,WAAW;IACvE,oCAAoC;IACpC,SAAS;IACT,SAAS;CACV,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA"}