@yugoo-design/dsh-design-studio-dsh 0.1.2 → 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 (56) hide show
  1. package/README.md +427 -3
  2. package/lib/authoring/panel-components.json +10790 -0
  3. package/lib/authoring/studio-creators/material.json +1 -0
  4. package/lib/authoring-agent-Ci08ifdJ.js +634 -0
  5. package/lib/authoring-agent.d.ts +24 -0
  6. package/lib/authoring-agent.js +2 -0
  7. package/lib/authoring-agent.types-QLa61eE6.d.ts +88 -0
  8. package/lib/authoring-entry.d.ts +20 -0
  9. package/lib/authoring-entry.js +77 -0
  10. package/lib/authoring-host-BlsA6wXz.js +347 -0
  11. package/lib/authoring-host.d.ts +5 -0
  12. package/lib/authoring-host.js +2 -0
  13. package/lib/authoring-host.types-d5UT0WEk.d.ts +77 -0
  14. package/lib/authoring-reference-store.d.ts +39 -0
  15. package/lib/authoring-reference-store.js +385 -0
  16. package/lib/authoring-service-qGEk-IE_.js +342 -0
  17. package/lib/authoring-service.d.ts +112 -0
  18. package/lib/authoring-service.js +2 -0
  19. package/lib/authoring-session.d.ts +5 -0
  20. package/lib/authoring-session.js +203 -0
  21. package/lib/authoring-session.types-MHEaEFkO.d.ts +58 -0
  22. package/lib/authoring-sources-BzRuEuub.d.ts +17 -0
  23. package/lib/authoring-sources.d.ts +2 -0
  24. package/lib/authoring-sources.js +85 -0
  25. package/lib/authoring-workbench.d.ts +15 -0
  26. package/lib/authoring-workbench.js +782 -0
  27. package/lib/authoring.page-rpc-Bg6VAT0Z.d.ts +30 -0
  28. package/lib/authoring.page-rpc.d.ts +2 -0
  29. package/lib/authoring.page-rpc.js +55 -0
  30. package/lib/authoring.remote-client.d.ts +2557 -0
  31. package/lib/authoring.remote-client.js +435 -0
  32. package/lib/client-adapter-BgD1mpCC.d.ts +65 -0
  33. package/lib/client-adapter.d.ts +2 -64
  34. package/lib/client.js +1 -2
  35. package/lib/css-assets/dsh-design-studio-page/9178281a0695f79ff8bff8eca529231b67f896dc1c12c8dbaaad24b32dd3fc07.eot +0 -0
  36. package/lib/css-assets/dsh-design-studio-page/b366faf39f6bcc48f10b10f2860e5ebe57091cb2a15beeed8a0977e24560bbba.svg +224 -0
  37. package/lib/css-assets/dsh-design-studio-page/manifest.json +20 -0
  38. package/lib/index.js +4 -105
  39. package/lib/kit-files-DKBlBOG8.js +159 -0
  40. package/lib/page-client.js +4 -23849
  41. package/lib/page-path-Bj5jO20L.d.ts +25 -0
  42. package/lib/page-path.d.ts +2 -2
  43. package/lib/page-path.js +70 -2
  44. package/lib/page-rpc-DiazVY00.d.ts +24 -0
  45. package/lib/page-rpc.d.ts +2 -15
  46. package/lib/page-rpc.js +9 -4
  47. package/lib/page.d.ts +5 -3
  48. package/lib/page.js +219 -2
  49. package/lib/remote-method-ab_lHwWN.js +16 -0
  50. package/lib/typert.host.d.ts +827 -2
  51. package/lib/typert.host.js +2 -1
  52. package/lib/typert.remote-client-CWz-anTr.d.ts +37 -0
  53. package/lib/typert.remote-client.d.ts +1 -36
  54. package/licenses/dsh-distribution.json +8644 -149
  55. package/package.json +73 -10
  56. package/lib/page-path-wn8e_fid.d.ts +0 -14
@@ -0,0 +1,25 @@
1
+ import { a as StudioPreparedLocator } from "./authoring-host.types-d5UT0WEk.js";
2
+ //#region src/page-path.d.ts
3
+ /** Base path owned by the Studio page route. */
4
+ declare const STUDIO_PAGE_BASE = "/design-studio/";
5
+ /** Slash-less route that redirects to the base path. */
6
+ declare const STUDIO_PAGE_PATH = "/design-studio";
7
+ /** Bundle file name served below the base path. */
8
+ declare const STUDIO_PAGE_BUNDLE = "page-client.js";
9
+ interface StudioAuthoringLocation {
10
+ readonly presentation: 'content-only' | 'full';
11
+ readonly initialInstance?: Pick<StudioPreparedLocator, 'id' | 'expectedRevision' | 'bundle'>;
12
+ readonly preparedLocator?: StudioPreparedLocator;
13
+ }
14
+ /** A locator for one parent-owned S2 receipt, never a save or identity capability. */
15
+ declare function studioPreparedUrl(agentId: string, locator: StudioPreparedLocator): string;
16
+ /** Parse exact navigation intent; the live Host revalidates its parent and candidate. */
17
+ declare function studioAuthoringLocation(href?: string | undefined): StudioAuthoringLocation;
18
+ /** Read only an explicit Session locator; the client codec and Host lookup validate it. */
19
+ declare function studioAuthoringSessionLocator(href?: string | undefined): string | undefined;
20
+ /** Recognize only the official Desktop application authority, not a redirect hint. */
21
+ declare function isDshDesktopLocation(href?: string | undefined): boolean;
22
+ /** Desktop returns through the known application root; no caller-controlled redirect. */
23
+ declare function studioConversationReturnPath(href?: string | undefined): '/' | undefined;
24
+ //#endregion
25
+ export { isDshDesktopLocation as a, studioConversationReturnPath as c, StudioAuthoringLocation as i, studioPreparedUrl as l, STUDIO_PAGE_BUNDLE as n, studioAuthoringLocation as o, STUDIO_PAGE_PATH as r, studioAuthoringSessionLocator as s, STUDIO_PAGE_BASE as t };
@@ -1,2 +1,2 @@
1
- import { a as studioConversationReturnPath, i as isDshDesktopLocation, n as STUDIO_PAGE_BUNDLE, r as STUDIO_PAGE_PATH, t as STUDIO_PAGE_BASE } from "./page-path-wn8e_fid.js";
2
- export { STUDIO_PAGE_BASE, STUDIO_PAGE_BUNDLE, STUDIO_PAGE_PATH, isDshDesktopLocation, studioConversationReturnPath };
1
+ import { a as isDshDesktopLocation, c as studioConversationReturnPath, i as StudioAuthoringLocation, l as studioPreparedUrl, n as STUDIO_PAGE_BUNDLE, o as studioAuthoringLocation, r as STUDIO_PAGE_PATH, s as studioAuthoringSessionLocator, t as STUDIO_PAGE_BASE } from "./page-path-Bj5jO20L.js";
2
+ export { STUDIO_PAGE_BASE, STUDIO_PAGE_BUNDLE, STUDIO_PAGE_PATH, StudioAuthoringLocation, isDshDesktopLocation, studioAuthoringLocation, studioAuthoringSessionLocator, studioConversationReturnPath, studioPreparedUrl };
package/lib/page-path.js CHANGED
@@ -1,11 +1,79 @@
1
1
  //#region src/page-path.ts
2
- /** Browser-safe path constants shared by the host route and the launcher. */
3
2
  /** Base path owned by the Studio page route. */
4
3
  const STUDIO_PAGE_BASE = "/design-studio/";
5
4
  /** Slash-less route that redirects to the base path. */
6
5
  const STUDIO_PAGE_PATH = "/design-studio";
7
6
  /** Bundle file name served below the base path. */
8
7
  const STUDIO_PAGE_BUNDLE = "page-client.js";
8
+ /** A locator for one parent-owned S2 receipt, never a save or identity capability. */
9
+ function studioPreparedUrl(agentId, locator) {
10
+ const { requestId, digest, ...target } = locator;
11
+ const query = new URLSearchParams({
12
+ agentId,
13
+ presentation: "full",
14
+ studioTarget: JSON.stringify(target),
15
+ studioRequest: requestId,
16
+ studioDigest: digest
17
+ });
18
+ return `${STUDIO_PAGE_BASE}?${query}`;
19
+ }
20
+ /** Parse exact navigation intent; the live Host revalidates its parent and candidate. */
21
+ function studioAuthoringLocation(href = globalThis.location?.href) {
22
+ if (href === void 0) return { presentation: "full" };
23
+ const url = new URL(href);
24
+ const one = (name) => {
25
+ const values = url.searchParams.getAll(name);
26
+ if (values.length > 1) throw new Error(`Studio requires one ${name} locator`);
27
+ return values[0];
28
+ };
29
+ const presentation = one("presentation") ?? "full";
30
+ if (presentation !== "content-only" && presentation !== "full") throw new Error("Unknown Studio presentation");
31
+ const raw = one("studioTarget"), requestId = one("studioRequest"), digest = one("studioDigest");
32
+ if (raw === void 0 && requestId === void 0 && digest === void 0) return { presentation };
33
+ if (!studioAuthoringSessionLocator(href)) throw new Error("Studio candidate navigation requires an explicit native Session");
34
+ const validIdentity = (value) => typeof value === "string" && value.length > 0 && !/[\u0000-\u001f\u007f]/u.test(value);
35
+ const object = (value) => !!value && typeof value === "object" && !Array.isArray(value);
36
+ if (raw === void 0 || raw.length > 8192) throw new Error("Studio target is missing or too large");
37
+ const value = JSON.parse(raw);
38
+ if (!object(value) || Object.keys(value).some((key) => ![
39
+ "id",
40
+ "expectedRevision",
41
+ "bundle"
42
+ ].includes(key)) || !validIdentity(value.id) || !Number.isSafeInteger(value.expectedRevision) || Number(value.expectedRevision) < 0) throw new Error("Invalid Studio target");
43
+ const pin = value.bundle;
44
+ if (pin !== void 0 && (!object(pin) || Object.keys(pin).some((key) => !["name", "version"].includes(key)) || !validIdentity(pin.name) || !validIdentity(pin.version))) throw new Error("Invalid Studio Bundle pin");
45
+ const initialInstance = {
46
+ id: value.id,
47
+ expectedRevision: value.expectedRevision,
48
+ ...pin === void 0 ? {} : { bundle: {
49
+ name: pin.name,
50
+ version: pin.version
51
+ } }
52
+ };
53
+ if (requestId === void 0 && digest === void 0) return {
54
+ presentation,
55
+ initialInstance
56
+ };
57
+ if (!validIdentity(requestId) || requestId.length > 128 || digest === void 0 || !/^[a-f0-9]{64}$/u.test(digest)) throw new Error("Invalid Studio candidate locator");
58
+ return {
59
+ presentation,
60
+ initialInstance,
61
+ preparedLocator: {
62
+ ...initialInstance,
63
+ requestId,
64
+ digest
65
+ }
66
+ };
67
+ }
68
+ /** Read only an explicit Session locator; the client codec and Host lookup validate it. */
69
+ function studioAuthoringSessionLocator(href = globalThis.location?.href) {
70
+ if (href === void 0) return void 0;
71
+ const url = new URL(href);
72
+ const locators = url.searchParams.getAll("agentId");
73
+ if (!locators.length) return void 0;
74
+ if (locators.length !== 1 || url.pathname !== "/design-studio/" && url.pathname !== "/design-studio") throw new Error("Studio authoring requires one explicit Session locator on its owned page");
75
+ return locators[0];
76
+ }
9
77
  /** Recognize only the official Desktop application authority, not a redirect hint. */
10
78
  function isDshDesktopLocation(href = globalThis.location?.href) {
11
79
  if (href === void 0) return false;
@@ -23,4 +91,4 @@ function studioConversationReturnPath(href = globalThis.location?.href) {
23
91
  return pathname === "/design-studio/" || pathname === "/design-studio" ? "/" : void 0;
24
92
  }
25
93
  //#endregion
26
- export { STUDIO_PAGE_BASE, STUDIO_PAGE_BUNDLE, STUDIO_PAGE_PATH, isDshDesktopLocation, studioConversationReturnPath };
94
+ export { STUDIO_PAGE_BASE, STUDIO_PAGE_BUNDLE, STUDIO_PAGE_PATH, isDshDesktopLocation, studioAuthoringLocation, studioAuthoringSessionLocator, studioConversationReturnPath, studioPreparedUrl };
@@ -0,0 +1,24 @@
1
+ import { n as DshStudioRemote } from "./client-adapter-BgD1mpCC.js";
2
+ import { r as StudioStrictCodec } from "./typert.remote-client-CWz-anTr.js";
3
+ //#region src/page-rpc.d.ts
4
+ /** Page transport with the same signature as the global fetch. */
5
+ type StudioPageFetch = (input: URL, init: RequestInit) => Promise<Response>;
6
+ /** Shared carrier for the existing read namespace and optional scoped methods. */
7
+ interface StudioPageRpcDescriptor {
8
+ readonly namespace: string;
9
+ readonly method: string;
10
+ readonly result: StudioStrictCodec;
11
+ }
12
+ /** One session-free read-only studioManagement method. */
13
+ type StudioRpcMethod = 'workspace' | 'readKitWorkspace' | 'readKitFiles' | 'readKitFile' | 'readBundleWorkspace' | 'readBundleFile';
14
+ /** Same Host wire, correlation, strict result and custom-scheme proxy for every Studio namespace. */
15
+ declare function callStudioRemote<T>(descriptor: StudioPageRpcDescriptor, args: Record<string, unknown>, doFetch: StudioPageFetch): Promise<T>;
16
+ /**
17
+ * Build the session-free Studio Remote over one same-origin /api transport.
18
+ * No DSH client plugin, runtime, or module loader boots for this page.
19
+ * @param doFetch - transport override for tests; defaults to the page fetch.
20
+ * @returns the Remote namespace consumed by the shared client adapter.
21
+ */
22
+ declare function createSameOriginStudioRemote(doFetch?: StudioPageFetch): DshStudioRemote;
23
+ //#endregion
24
+ export { createSameOriginStudioRemote as a, callStudioRemote as i, StudioPageRpcDescriptor as n, StudioRpcMethod as r, StudioPageFetch as t };
package/lib/page-rpc.d.ts CHANGED
@@ -1,15 +1,2 @@
1
- import { DshStudioRemote } from "./client-adapter.js";
2
- //#region src/page-rpc.d.ts
3
- /** Page transport with the same signature as the global fetch. */
4
- type StudioPageFetch = (input: URL, init: RequestInit) => Promise<Response>;
5
- /** One session-free read-only studioManagement method. */
6
- type StudioRpcMethod = 'workspace' | 'readKitWorkspace' | 'readKitFiles' | 'readKitFile' | 'readBundleWorkspace' | 'readBundleFile';
7
- /**
8
- * Build the session-free Studio Remote over one same-origin /api transport.
9
- * No DSH client plugin, runtime, or module loader boots for this page.
10
- * @param doFetch - transport override for tests; defaults to the page fetch.
11
- * @returns the Remote namespace consumed by the shared client adapter.
12
- */
13
- declare function createSameOriginStudioRemote(doFetch?: StudioPageFetch): DshStudioRemote;
14
- //#endregion
15
- export { StudioPageFetch, StudioRpcMethod, createSameOriginStudioRemote };
1
+ import { a as createSameOriginStudioRemote, i as callStudioRemote, n as StudioPageRpcDescriptor, r as StudioRpcMethod, t as StudioPageFetch } from "./page-rpc-DiazVY00.js";
2
+ export { StudioPageFetch, StudioPageRpcDescriptor, StudioRpcMethod, callStudioRemote, createSameOriginStudioRemote };
package/lib/page-rpc.js CHANGED
@@ -1,5 +1,5 @@
1
- import { isDshDesktopLocation } from "./page-path.js";
2
1
  import { TYPERT_REMOTE } from "./typert.remote-client.js";
2
+ import { isDshDesktopLocation } from "./page-path.js";
3
3
  import { z } from "zod";
4
4
  //#region src/page-rpc.ts
5
5
  /** Thin same-origin RPC transport for the standalone Studio page. */
@@ -40,14 +40,19 @@ function resolveBase() {
40
40
  async function callStudioManagement(method, args, doFetch) {
41
41
  const descriptor = TYPERT_REMOTE.descriptors.find((candidate) => candidate.method === method);
42
42
  if (descriptor === void 0) throw new Error(`studio page rpc: unknown method ${JSON.stringify(method)}`);
43
+ return callStudioRemote(descriptor, args, doFetch);
44
+ }
45
+ /** Same Host wire, correlation, strict result and custom-scheme proxy for every Studio namespace. */
46
+ async function callStudioRemote(descriptor, args, doFetch) {
47
+ const { namespace, method } = descriptor;
43
48
  const rpcId = crypto.randomUUID();
44
- const response = await doFetch(new URL(`/api/studioManagement/${method}`, resolveBase()), {
49
+ const response = await doFetch(new URL(`/api/${namespace}/${method}`, resolveBase()), {
45
50
  method: "POST",
46
51
  headers: { "content-type": "application/json" },
47
52
  body: JSON.stringify({
48
53
  type: "client-request",
49
54
  rpcId,
50
- method: `studioManagement/${method}`,
55
+ method: `${namespace}/${method}`,
51
56
  payload: { args }
52
57
  })
53
58
  });
@@ -100,4 +105,4 @@ function createSameOriginStudioRemote(doFetch = (input, init) => globalThis.fetc
100
105
  });
101
106
  }
102
107
  //#endregion
103
- export { createSameOriginStudioRemote };
108
+ export { callStudioRemote, createSameOriginStudioRemote };
package/lib/page.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { r as STUDIO_PAGE_PATH, t as STUDIO_PAGE_BASE } from "./page-path-wn8e_fid.js";
1
+ import { r as STUDIO_PAGE_PATH, t as STUDIO_PAGE_BASE } from "./page-path-Bj5jO20L.js";
2
+ import { IncomingMessage, ServerResponse } from "node:http";
2
3
  //#region src/page.d.ts
3
4
  /** Minimal WebServer surface consumed by this plugin. */
4
5
  interface StudioPageWebServer {
@@ -6,18 +7,19 @@ interface StudioPageWebServer {
6
7
  register(route: {
7
8
  readonly kind: 'exact' | 'prefix';
8
9
  readonly path: string;
9
- readonly handler: (req: StudioPageRequest, res: StudioPageResponse) => void | Promise<void>;
10
+ readonly handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;
10
11
  }): () => void;
11
12
  }
12
13
  /** Incoming request slice read by the Studio route. */
13
14
  interface StudioPageRequest {
14
15
  readonly url?: string;
16
+ readonly method?: string;
15
17
  }
16
18
  /** Server response slice written by the Studio route. */
17
19
  interface StudioPageResponse {
18
20
  statusCode: number;
19
21
  setHeader(name: string, value: string): void;
20
- getHeader?(name: string): string | number | undefined;
22
+ getHeader?(name: string): string | number | readonly string[] | undefined;
21
23
  end(body?: string | Uint8Array): void;
22
24
  }
23
25
  /** Cordis plugin name. */
package/lib/page.js CHANGED
@@ -1,5 +1,219 @@
1
1
  import { STUDIO_PAGE_BASE, STUDIO_PAGE_BUNDLE, STUDIO_PAGE_PATH } from "./page-path.js";
2
+ import { join } from "node:path";
3
+ import { createHash } from "node:crypto";
2
4
  import { readFile } from "node:fs/promises";
5
+ import { closeSync, constants, fstatSync, lstatSync, openSync, readSync, realpathSync } from "node:fs";
6
+ import { fileURLToPath } from "node:url";
7
+ import { TextDecoder } from "node:util";
8
+ //#region ../../scripts/css-asset-route.mjs
9
+ const MIME = Object.freeze({
10
+ eot: "application/vnd.ms-fontobject",
11
+ svg: "image/svg+xml",
12
+ ttf: "font/ttf",
13
+ otf: "font/otf",
14
+ woff: "font/woff",
15
+ woff2: "font/woff2",
16
+ png: "image/png",
17
+ jpg: "image/jpeg",
18
+ jpeg: "image/jpeg",
19
+ gif: "image/gif",
20
+ webp: "image/webp",
21
+ avif: "image/avif"
22
+ });
23
+ /** Serve only a verified, immutable CSS asset table, never a directory.
24
+ * Mount behind the owning host's existing access policy. This helper does not
25
+ * supply authentication, register a host route, or certify a packaged build.
26
+ */
27
+ function createCssAssetHandler({ prefix, assets }) {
28
+ if (typeof prefix !== "string" || !/^\/design-system-assets\/[a-z0-9-]+\/$/u.test(prefix)) throw Error("Invalid owned CSS asset prefix");
29
+ if (!Array.isArray(assets) || assets.length > 512) throw Error("CSS asset table exceeds budget");
30
+ const table = /* @__PURE__ */ new Map();
31
+ let total = 0;
32
+ for (const asset of assets) {
33
+ if (!Buffer.isBuffer(asset?.bytes) || asset.bytes.length > 33554432) throw Error("Invalid CSS asset bytes or budget");
34
+ total += asset.bytes.length;
35
+ if (total > 67108864) throw Error("CSS asset table bytes exceed budget");
36
+ const match = /^([a-f0-9]{64})\.([a-z0-9]+)$/u.exec(asset.name ?? "");
37
+ if (!match || !Object.hasOwn(MIME, match[2]) || asset.mime !== MIME[match[2]]) throw Error("Invalid CSS asset identity or MIME");
38
+ const bytes = Buffer.from(asset.bytes);
39
+ const hash = createHash("sha256").update(bytes).digest("hex");
40
+ if (hash !== match[1] || asset.sha256 !== hash) throw Error("CSS asset bytes do not match content identity");
41
+ if (table.has(asset.name)) throw Error("Duplicate CSS asset identity");
42
+ table.set(asset.name, {
43
+ bytes,
44
+ mime: asset.mime,
45
+ etag: `"${hash}"`
46
+ });
47
+ }
48
+ return function cssAssetHandler(request, response) {
49
+ response.setHeader("X-Content-Type-Options", "nosniff");
50
+ if (!["GET", "HEAD"].includes(request.method)) {
51
+ response.statusCode = 405;
52
+ response.setHeader("Allow", "GET, HEAD");
53
+ response.end();
54
+ return;
55
+ }
56
+ const url = request.url;
57
+ let name;
58
+ if (typeof url === "string" && url.startsWith(prefix) && !/[\u0000-\u0020\u007f#]/u.test(url)) {
59
+ const pathname = url.split("?", 1)[0];
60
+ try {
61
+ name = decodeURIComponent(pathname.slice(prefix.length));
62
+ } catch {}
63
+ }
64
+ const asset = typeof name === "string" && /^[a-f0-9]{64}\.[a-z0-9]+$/u.test(name) ? table.get(name) : void 0;
65
+ if (!asset) {
66
+ response.statusCode = 404;
67
+ response.end();
68
+ return;
69
+ }
70
+ response.statusCode = 200;
71
+ response.setHeader("Content-Type", asset.mime);
72
+ response.setHeader("Content-Length", asset.bytes.length);
73
+ response.setHeader("Cache-Control", "public, max-age=31536000, immutable");
74
+ response.setHeader("ETag", asset.etag);
75
+ response.end(request.method === "HEAD" ? void 0 : asset.bytes);
76
+ };
77
+ }
78
+ //#endregion
79
+ //#region ../../scripts/css-asset-manifest.mjs
80
+ function ownedRead(root, relative, limit) {
81
+ let file = root;
82
+ const parents = [{
83
+ path: root,
84
+ info: lstatSync(root)
85
+ }];
86
+ const verifyParents = () => {
87
+ for (const parent of parents) {
88
+ const current = lstatSync(parent.path);
89
+ if (!current.isDirectory() || current.isSymbolicLink() || current.dev !== parent.info.dev || current.ino !== parent.info.ino || realpathSync(parent.path) !== parent.path) throw Error("Packaged CSS parent ownership changed");
90
+ }
91
+ if (realpathSync(file) !== file) throw Error("Packaged CSS resolved ownership changed");
92
+ };
93
+ const parts = relative.split("/");
94
+ for (const [index, part] of parts.entries()) {
95
+ if (!part || part === "." || part === ".." || /[\\\u0000-\u001f]/u.test(part)) throw Error("Invalid packaged CSS asset path");
96
+ file = join(file, part);
97
+ const info = lstatSync(file);
98
+ if (info.isSymbolicLink() || !(index === parts.length - 1 ? info.isFile() : info.isDirectory())) throw Error("CSS package assets must be regular owned files");
99
+ if (index !== parts.length - 1) parents.push({
100
+ path: file,
101
+ info
102
+ });
103
+ }
104
+ const checked = lstatSync(file);
105
+ verifyParents();
106
+ const fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
107
+ try {
108
+ const info = fstatSync(fd);
109
+ verifyParents();
110
+ if (!info.isFile() || info.dev !== checked.dev || info.ino !== checked.ino || info.size !== checked.size) throw Error("Packaged CSS asset identity changed");
111
+ if (info.size > limit) throw Error("Packaged CSS bytes exceed budget");
112
+ const bytes = Buffer.alloc(info.size + 1);
113
+ let length = 0;
114
+ while (length < bytes.length) {
115
+ const count = readSync(fd, bytes, length, bytes.length - length, null);
116
+ if (!count) break;
117
+ length += count;
118
+ }
119
+ const final = fstatSync(fd);
120
+ verifyParents();
121
+ if (length !== info.size || final.size !== info.size || final.mtimeMs !== info.mtimeMs || final.ctimeMs !== info.ctimeMs) throw Error("Packaged CSS asset changed");
122
+ return bytes.subarray(0, length);
123
+ } finally {
124
+ closeSync(fd);
125
+ }
126
+ }
127
+ /** Read only the current content-addressed manifest, never enumerate a directory.
128
+ * Stale build files are neither admitted nor exposed. The package root is an
129
+ * explicit owner-selected directory; native list paths are not accepted here.
130
+ */
131
+ function loadPackagedCssAssets(packageRoot, prefix) {
132
+ createCssAssetHandler({
133
+ prefix,
134
+ assets: []
135
+ });
136
+ const root = realpathSync(packageRoot);
137
+ const folder = `css-assets/${prefix.split("/")[2]}`;
138
+ const manifestBytes = ownedRead(root, `${folder}/manifest.json`, 262144);
139
+ const manifest = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(manifestBytes));
140
+ if (manifest?.schema !== "design-system-css-assets-v1" || manifest.prefix !== prefix || !Array.isArray(manifest.assets) || manifest.assets.length > 512) throw Error("Invalid packaged CSS asset manifest");
141
+ const seen = /* @__PURE__ */ new Set(), assets = [];
142
+ let total = 0;
143
+ for (const entry of manifest.assets) {
144
+ if (!/^[a-f0-9]{64}\.[a-z0-9]+$/u.test(entry?.name ?? "") || entry.file !== `${folder}/${entry.name}` || seen.has(entry.name)) throw Error("Invalid or duplicate packaged CSS identity");
145
+ if (!Number.isSafeInteger(entry.bytes) || entry.bytes < 0 || entry.bytes > 33554432) throw Error("Invalid packaged CSS byte budget");
146
+ total += entry.bytes;
147
+ if (total > 67108864) throw Error("Packaged CSS total exceeds budget");
148
+ seen.add(entry.name);
149
+ }
150
+ for (const entry of manifest.assets) {
151
+ const bytes = ownedRead(root, entry.file, entry.bytes);
152
+ if (bytes.length !== entry.bytes) throw Error("Packaged CSS byte length mismatch");
153
+ assets.push({
154
+ name: entry.name,
155
+ bytes,
156
+ sha256: entry.sha256,
157
+ mime: entry.mime
158
+ });
159
+ }
160
+ return {
161
+ handler: createCssAssetHandler({
162
+ prefix,
163
+ assets
164
+ }),
165
+ manifest,
166
+ assets
167
+ };
168
+ }
169
+ //#endregion
170
+ //#region ../../scripts/css-asset-host.mjs
171
+ /** Match the official webServer.register route/disposer contract.
172
+ * Asset tables are verified before the first registration. Call within the
173
+ * owning plugin lifecycle and existing host access policy; no fallback seat
174
+ * or upstream /plugins route is claimed by this helper.
175
+ */
176
+ function registerPackagedCssAssets(webServer, packageRoot, prefixes) {
177
+ if (typeof webServer?.register !== "function" || !Array.isArray(prefixes) || !prefixes.length || new Set(prefixes).size !== prefixes.length) throw Error("Explicit CSS route service and unique prefixes required");
178
+ const tables = prefixes.map((prefix) => ({
179
+ prefix,
180
+ ...loadPackagedCssAssets(packageRoot, prefix)
181
+ }));
182
+ const disposers = [];
183
+ try {
184
+ for (const table of tables) {
185
+ const dispose = webServer.register({
186
+ kind: "prefix",
187
+ path: table.prefix.slice(0, -1),
188
+ handler: table.handler
189
+ });
190
+ if (typeof dispose !== "function") throw Error("Host CSS registration did not return a disposer");
191
+ disposers.push(dispose);
192
+ }
193
+ } catch (error) {
194
+ const failures = [];
195
+ for (const dispose of disposers.reverse()) try {
196
+ dispose();
197
+ } catch (cleanupError) {
198
+ failures.push(cleanupError);
199
+ }
200
+ if (failures.length) throw new AggregateError([error, ...failures], "CSS route registration and rollback failed");
201
+ throw error;
202
+ }
203
+ let closed = false;
204
+ return () => {
205
+ if (closed) return;
206
+ closed = true;
207
+ const failures = [];
208
+ for (const dispose of disposers.reverse()) try {
209
+ dispose();
210
+ } catch (error) {
211
+ failures.push(error);
212
+ }
213
+ if (failures.length) throw new AggregateError(failures, "CSS route disposal failed");
214
+ };
215
+ }
216
+ //#endregion
3
217
  //#region src/page.ts
4
218
  /** Plugin-owned same-origin route serving the standalone Design Studio page. */
5
219
  /** Cordis plugin name. */
@@ -46,8 +260,10 @@ function shellHtml(base) {
46
260
  function apply(ctx) {
47
261
  const face = ctx;
48
262
  const bundleUrl = new URL(`./${STUDIO_PAGE_BUNDLE}`, import.meta.url);
49
- const redirect = async (_req, res) => {
50
- send(res, 308, "text/plain; charset=utf-8", "", { Location: STUDIO_PAGE_BASE });
263
+ const redirect = async (req, res) => {
264
+ const queryIndex = req.url?.indexOf("?") ?? -1;
265
+ const query = queryIndex < 0 ? "" : req.url.slice(queryIndex);
266
+ send(res, 308, "text/plain; charset=utf-8", "", { Location: STUDIO_PAGE_BASE + query });
51
267
  };
52
268
  const handler = async (req, res) => {
53
269
  const path = requestPath(req);
@@ -73,6 +289,7 @@ function apply(ctx) {
73
289
  face.effect(() => {
74
290
  const acquired = [];
75
291
  try {
292
+ acquired.push(registerPackagedCssAssets(face.webServer, fileURLToPath(new URL(".", import.meta.url)), ["/design-system-assets/dsh-design-studio-page/"]));
76
293
  acquired.push(face.webServer.register({
77
294
  kind: "exact",
78
295
  path: STUDIO_PAGE_PATH,
@@ -0,0 +1,16 @@
1
+ import { Remote } from "@deepseek-ai/dsh-typert-protocol";
2
+ //#region src/remote-method.ts
3
+ /** The public SDK marker, without requiring runtime decorator syntax. */
4
+ function markStudioRemote(prototype, name, method) {
5
+ Remote(method, {
6
+ kind: "method",
7
+ name,
8
+ static: false,
9
+ private: false,
10
+ addInitializer(initializer) {
11
+ initializer.call(Object.create(prototype));
12
+ }
13
+ });
14
+ }
15
+ //#endregion
16
+ export { markStudioRemote as t };