deepspace 0.22.1 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # deepspace
2
2
 
3
+ ## 0.23.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Fix the six top findings from the five-app AX audit: enforce chat ownership in `getChat` (a signed-in user could rename, delete, or write another user's AI chat), make failed writes observable instead of silently succeeding (system-managed column updates, queries against a collection the room does not carry, and writes attempted before the room is ready — now a `not_ready` `WriteError`), key the Playwright storage-state cache by origin and validate the session before reuse (raising `TestSignInError` rather than returning a signed-out context), resolve the browser bundle's app id from the wrangler config being built so `deploy --env` cannot ship a bundle wired to another environment, add `deepspace test accounts recover` so pool accounts created on another machine are usable, and ship a scaffold whose `collab.spec.ts` passes on a fresh app.
8
+
9
+ **Upgrading — apps scaffolded on 0.21.0–0.22.1.** Those scaffolds baked a literal app id into `src/constants.ts` and carry no `deepspace/build` wiring. They keep working untouched: plain `deploy` passes (the new bundle guard refuses only a FOREIGN id), and already-deployed apps are unaffected. What changes: `deploy --env <name>` is now **refused** (`app_id_env_mismatch`) instead of silently shipping that environment's worker against production's rooms — the refusal lists the three file edits that adopt the build-time id (`vite.config.ts` → `deepspaceBuild()`, `vitest.config.ts` → `appIdDefine()` if present, `src/constants.ts` → `__DEEPSPACE_APP_ID__`); there is deliberately no automatic migration for these app-owned files. Relatedly, `app init` no longer stamps ids into source files at all — wrangler.toml is the id's only home, and the client resolves it at build time. (The unknown-collection suffix below applies to the `resolveCollection` paths — subscription queries, record tools, and Yjs collection checks.)
10
+
11
+ **Upgrading.** Two signatures changed: `getStatePathForEmail` and `readCachedState` from `deepspace/testing` now take `(email, baseURL)`, since the cache is keyed by origin as well as account. `WriteError.kind` gains `'not_ready'`, which only breaks an exhaustive `switch` over the union.
12
+
13
+ **Two changes reach code the SDK cannot upgrade for you**, because worker routes are vendored into your repo at scaffold time. First, the unknown-collection error text gained a suffix naming the room and the collections it serves; it now reads `Schema not registered for collection: <name> — Room '<scope>' serves only: …`. The shipped template and the data-boundary migration guide both match it with `startsWith`, so supported code is unaffected, but a vendored route comparing with `===` stops matching and falls through to its error branch — in `getDocumentForAccess` that is a 403 on every Yjs document room. Change any such comparison to `startsWith`. Second, `records.query` against a collection the room does not carry now returns `{ success: false }` instead of `{ success: true, records: [] }`; since `executeTool` throws on `!success`, a cron handler or server action querying an optional collection now fails its job rather than seeing an empty list. Guard those call sites, or register the collection in the room.
14
+
15
+ **Scope of the system-column refusal.** It covers the `users` columns you can mean to set — `email`, `name`, `imageUrl` and `role`. The server-maintained timestamps `createdAt` and `lastSeenAt` are still preserved silently: the server rewrites `lastSeenAt` on every connect and every presence heartbeat without broadcasting, so a whole-record echo carries a stale value the caller never chose and has no way to refresh. Sending any system column at its current stored value still succeeds, and `null`, `''` and omitting the field are treated as the one stored state they are.
16
+
3
17
  ## 0.22.1
4
18
 
5
19
  ### Patch Changes
@@ -0,0 +1,115 @@
1
+ /**
2
+ * `deepspace/build` — build-time helpers an app's Vite config imports.
3
+ *
4
+ * Node-only. Nothing here may be reached from client or worker code; it runs
5
+ * while `vite build` / `vite dev` is assembling the bundle, never inside it.
6
+ *
7
+ * The one job today is resolving the app's immutable id for the browser.
8
+ * `wrangler.toml` is the single source of truth for app identity — the worker
9
+ * reads `DEEPSPACE_APP_ID` off `env`, and every durable thing (data scope
10
+ * `app:{appId}`, secrets, billing) keys to it. The browser has no `env`, so
11
+ * the build stamps the same value in as `__DEEPSPACE_APP_ID__`, read from the
12
+ * config THIS build is running against. A literal baked into the client once,
13
+ * at scaffold time, is what let `deepspace deploy --env staging` ship a worker
14
+ * writing staging's rooms beside a browser reading production's, with nothing
15
+ * reporting the split.
16
+ *
17
+ * Living in the SDK rather than in a file copied into every app means one
18
+ * definition, no per-app duplicate, and `smol-toml` stays the SDK's dependency
19
+ * instead of every app's.
20
+ */
21
+ /**
22
+ * The build's environment, typed structurally. This module is imported from an
23
+ * app's `vite.config.ts`, which sits outside the app's tsconfig `include` and
24
+ * runs where the app carries no `@types/node`, so nothing here leans on the
25
+ * ambient `NodeJS` namespace.
26
+ */
27
+ type BuildEnv = Record<string, string | undefined>;
28
+ interface ResolveAppIdOptions {
29
+ /**
30
+ * The app root — the directory holding `wrangler.toml`. The caller passes
31
+ * its own location (`fileURLToPath(new URL('.', import.meta.url))` from
32
+ * `vite.config.ts`), so the config is found by an authoritative anchor
33
+ * rather than a guessed `process.cwd()`.
34
+ */
35
+ appDir: string;
36
+ /** Defaults to `process.env`; injectable for tests. */
37
+ env?: BuildEnv;
38
+ }
39
+ /**
40
+ * Resolve the app id the browser bundle must carry.
41
+ *
42
+ * Mirrors the Cloudflare Vite plugin so dev, a bare `vite build`, and
43
+ * `deepspace deploy --env <name>` all agree:
44
+ *
45
+ * config ← CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH, else <appDir>/wrangler.toml
46
+ * section ← [env.<CLOUDFLARE_ENV>.vars] when that is set, else [vars]
47
+ *
48
+ * Env blocks do NOT inherit `[vars]` — each environment is its own app — so a
49
+ * missing or malformed id throws and fails the build. It never falls back to
50
+ * another environment's id: a fallback that silently picks the wrong one is
51
+ * exactly the bug this exists to remove.
52
+ */
53
+ declare function resolveAppId({ appDir, env }: ResolveAppIdOptions): string;
54
+ /**
55
+ * Vite `define` entry consumed by an app's `vite.config.ts` and
56
+ * `vitest.config.ts`, so unit tests see the same id the bundle is built with:
57
+ *
58
+ * ```ts
59
+ * import { fileURLToPath } from 'node:url'
60
+ * import { appIdDefine } from 'deepspace/build'
61
+ * export default defineConfig({
62
+ * define: appIdDefine({ appDir: fileURLToPath(new URL('.', import.meta.url)) }),
63
+ * })
64
+ * ```
65
+ */
66
+ declare function appIdDefine(options: ResolveAppIdOptions): Record<string, string>;
67
+
68
+ /**
69
+ * `deepspaceBuild()` — the one Vite plugin an app's build config needs.
70
+ *
71
+ * It carries the build-time wiring that is identical in every DeepSpace app
72
+ * and belongs to the SDK's contract rather than to app code:
73
+ *
74
+ * 1. the app id `define` (see ./app-id) — so the browser is keyed to the
75
+ * environment THIS build targets, never a literal frozen at scaffold time;
76
+ * 2. removing the preview `.dev.vars` the Cloudflare plugin drops beside the
77
+ * built worker — DeepSpace has one local runtime, so that second plaintext
78
+ * copy is not kept;
79
+ * 3. the client `dedupe` hint — two copies of React (or better-auth) break
80
+ * the SDK's hooks, and which packages must be single-instance is the SDK's
81
+ * knowledge, not the app's.
82
+ *
83
+ * Housing these here means a future fix to any of them ships in an SDK version
84
+ * bump, with no edit to — and no migration of — a single app.
85
+ *
86
+ * The return is a structural Vite plugin: typing it against `vite` would make
87
+ * the SDK carry a Vite dependency it otherwise does not need, and Vite's
88
+ * plugin hooks are optional and method-shaped, so a compatible object is
89
+ * assignable to `PluginOption` where an app composes it.
90
+ */
91
+
92
+ /**
93
+ * Packages that must resolve to a single instance in the client graph. Two
94
+ * Reacts throw "Cannot read properties of null (reading 'useState')"; two
95
+ * better-auth clients split the session. The SDK owns this list because it is
96
+ * the SDK's hooks and auth client that break when it is wrong.
97
+ */
98
+ declare const CLIENT_DEDUPE: readonly string[];
99
+ /** The subset of Vite's `Plugin` this helper implements — declared locally so
100
+ * `deepspace/build` needs no `vite` dependency. Assignable to `PluginOption`. */
101
+ interface VitePluginLike {
102
+ name: string;
103
+ enforce?: 'pre' | 'post';
104
+ config(): {
105
+ define: Record<string, string>;
106
+ resolve: {
107
+ dedupe: string[];
108
+ };
109
+ };
110
+ closeBundle(): void;
111
+ }
112
+ type DeepspaceBuildOptions = ResolveAppIdOptions;
113
+ declare function deepspaceBuild(options: DeepspaceBuildOptions): VitePluginLike;
114
+
115
+ export { type BuildEnv, CLIENT_DEDUPE, type DeepspaceBuildOptions, type ResolveAppIdOptions, appIdDefine, deepspaceBuild, resolveAppId };
package/dist/build.js ADDED
@@ -0,0 +1,66 @@
1
+ // src/build/app-id.ts
2
+ import { readFileSync } from "fs";
3
+ import { resolve } from "path";
4
+ import { parse as parseToml } from "smol-toml";
5
+
6
+ // src/server/utils/registry-client.ts
7
+ var APP_ID_RE = /^app_[0-9A-HJKMNP-TV-Z]{26}$/;
8
+
9
+ // src/build/app-id.ts
10
+ function resolveAppId({ appDir, env = process.env }) {
11
+ const configPath = env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH ? resolve(env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH) : resolve(appDir, "wrangler.toml");
12
+ const envName = env.CLOUDFLARE_ENV;
13
+ const section = envName ? `[env.${envName}.vars]` : "[vars]";
14
+ let config;
15
+ try {
16
+ config = parseToml(readFileSync(configPath, "utf-8"));
17
+ } catch (error) {
18
+ throw new Error(
19
+ `Could not read the app id: ${configPath} is missing or not valid TOML (${error instanceof Error ? error.message : String(error)}).`
20
+ );
21
+ }
22
+ const appId = (envName ? config.env?.[envName]?.vars : config.vars)?.DEEPSPACE_APP_ID;
23
+ if (typeof appId !== "string" || !APP_ID_RE.test(appId)) {
24
+ const uninitialized = appId === void 0 || appId === "__APP_ID__";
25
+ throw new Error(
26
+ `${configPath}: ${section} has no valid DEEPSPACE_APP_ID (found ${appId === void 0 ? "nothing" : JSON.stringify(appId)}). The browser keys its RecordRooms to this id, so the build stops rather than ship a wrong or empty one. ` + (uninitialized ? `Run \`npx deepspace app init${envName ? ` --env ${envName}` : ""}\` to register this app \u2014 ids are server-minted; never hand-write one.` : `Add the id your \`deepspace app init\` registered under ${section}.`) + (envName ? ` Environments do not inherit the top-level id; each one is its own app.` : ``)
27
+ );
28
+ }
29
+ return appId;
30
+ }
31
+ function appIdDefine(options) {
32
+ return { __DEEPSPACE_APP_ID__: JSON.stringify(resolveAppId(options)) };
33
+ }
34
+
35
+ // src/build/plugin.ts
36
+ import { existsSync, readdirSync, unlinkSync } from "fs";
37
+ import { join, resolve as resolve2 } from "path";
38
+ var CLIENT_DEDUPE = ["react", "react-dom", "better-auth"];
39
+ function deepspaceBuild(options) {
40
+ const distDir = resolve2(options.appDir, "dist");
41
+ return {
42
+ name: "deepspace-build",
43
+ enforce: "post",
44
+ config() {
45
+ return {
46
+ define: appIdDefine(options),
47
+ resolve: { dedupe: [...CLIENT_DEDUPE] }
48
+ };
49
+ },
50
+ closeBundle() {
51
+ if (!existsSync(distDir)) return;
52
+ for (const entry of readdirSync(distDir, { withFileTypes: true })) {
53
+ if (!entry.isDirectory()) continue;
54
+ const secretPath = join(distDir, entry.name, ".dev.vars");
55
+ if (existsSync(secretPath)) unlinkSync(secretPath);
56
+ }
57
+ }
58
+ };
59
+ }
60
+ export {
61
+ CLIENT_DEDUPE,
62
+ appIdDefine,
63
+ deepspaceBuild,
64
+ resolveAppId
65
+ };
66
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/build/app-id.ts","../src/server/utils/registry-client.ts","../src/build/plugin.ts"],"sourcesContent":["/**\n * `deepspace/build` — build-time helpers an app's Vite config imports.\n *\n * Node-only. Nothing here may be reached from client or worker code; it runs\n * while `vite build` / `vite dev` is assembling the bundle, never inside it.\n *\n * The one job today is resolving the app's immutable id for the browser.\n * `wrangler.toml` is the single source of truth for app identity — the worker\n * reads `DEEPSPACE_APP_ID` off `env`, and every durable thing (data scope\n * `app:{appId}`, secrets, billing) keys to it. The browser has no `env`, so\n * the build stamps the same value in as `__DEEPSPACE_APP_ID__`, read from the\n * config THIS build is running against. A literal baked into the client once,\n * at scaffold time, is what let `deepspace deploy --env staging` ship a worker\n * writing staging's rooms beside a browser reading production's, with nothing\n * reporting the split.\n *\n * Living in the SDK rather than in a file copied into every app means one\n * definition, no per-app duplicate, and `smol-toml` stays the SDK's dependency\n * instead of every app's.\n */\n\nimport { readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { parse as parseToml } from 'smol-toml'\n\n/** `app_` + 26-char Crockford ULID. */\n// Single source: the shared registry client owns the id shape.\nimport { APP_ID_RE } from '../server/utils/registry-client'\n\ninterface WranglerIdentity {\n vars?: Record<string, unknown>\n env?: Record<string, { vars?: Record<string, unknown> } | undefined>\n}\n\n/**\n * The build's environment, typed structurally. This module is imported from an\n * app's `vite.config.ts`, which sits outside the app's tsconfig `include` and\n * runs where the app carries no `@types/node`, so nothing here leans on the\n * ambient `NodeJS` namespace.\n */\nexport type BuildEnv = Record<string, string | undefined>\n\nexport interface ResolveAppIdOptions {\n /**\n * The app root — the directory holding `wrangler.toml`. The caller passes\n * its own location (`fileURLToPath(new URL('.', import.meta.url))` from\n * `vite.config.ts`), so the config is found by an authoritative anchor\n * rather than a guessed `process.cwd()`.\n */\n appDir: string\n /** Defaults to `process.env`; injectable for tests. */\n env?: BuildEnv\n}\n\n/**\n * Resolve the app id the browser bundle must carry.\n *\n * Mirrors the Cloudflare Vite plugin so dev, a bare `vite build`, and\n * `deepspace deploy --env <name>` all agree:\n *\n * config ← CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH, else <appDir>/wrangler.toml\n * section ← [env.<CLOUDFLARE_ENV>.vars] when that is set, else [vars]\n *\n * Env blocks do NOT inherit `[vars]` — each environment is its own app — so a\n * missing or malformed id throws and fails the build. It never falls back to\n * another environment's id: a fallback that silently picks the wrong one is\n * exactly the bug this exists to remove.\n */\nexport function resolveAppId({ appDir, env = process.env }: ResolveAppIdOptions): string {\n const configPath = env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH\n ? resolve(env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH)\n : resolve(appDir, 'wrangler.toml')\n const envName = env.CLOUDFLARE_ENV\n const section = envName ? `[env.${envName}.vars]` : '[vars]'\n\n let config: WranglerIdentity\n try {\n config = parseToml(readFileSync(configPath, 'utf-8')) as WranglerIdentity\n } catch (error) {\n throw new Error(\n `Could not read the app id: ${configPath} is missing or not valid TOML ` +\n `(${error instanceof Error ? error.message : String(error)}).`,\n )\n }\n\n const appId = (envName ? config.env?.[envName]?.vars : config.vars)?.DEEPSPACE_APP_ID\n if (typeof appId !== 'string' || !APP_ID_RE.test(appId)) {\n // A fresh-but-uninitialized scaffold (signed out at scaffold time) still\n // carries the __APP_ID__ placeholder — the remedy is registration, not\n // hand-writing an id (ids are server-minted at `deepspace app init`).\n const uninitialized = appId === undefined || appId === '__APP_ID__'\n throw new Error(\n `${configPath}: ${section} has no valid DEEPSPACE_APP_ID ` +\n `(found ${appId === undefined ? 'nothing' : JSON.stringify(appId)}). ` +\n `The browser keys its RecordRooms to this id, so the build stops rather than ship a wrong or empty one. ` +\n (uninitialized\n ? `Run \\`npx deepspace app init${envName ? ` --env ${envName}` : ''}\\` to register this app — ids are server-minted; never hand-write one.`\n : `Add the id your \\`deepspace app init\\` registered under ${section}.`) +\n (envName ? ` Environments do not inherit the top-level id; each one is its own app.` : ``),\n )\n }\n return appId\n}\n\n/**\n * Vite `define` entry consumed by an app's `vite.config.ts` and\n * `vitest.config.ts`, so unit tests see the same id the bundle is built with:\n *\n * ```ts\n * import { fileURLToPath } from 'node:url'\n * import { appIdDefine } from 'deepspace/build'\n * export default defineConfig({\n * define: appIdDefine({ appDir: fileURLToPath(new URL('.', import.meta.url)) }),\n * })\n * ```\n */\nexport function appIdDefine(options: ResolveAppIdOptions): Record<string, string> {\n return { __DEEPSPACE_APP_ID__: JSON.stringify(resolveAppId(options)) }\n}\n","/**\n * The ONE registry client. Every platform worker reaches the app-registry\n * Durable Object (hosted by the deploy worker, singleton `v1:global`) through\n * a cross-script REGISTRY binding — and before this module, each of the five\n * workers hand-rolled its own copy of the same mechanics: the instance name\n * appeared in 6 places, the `https://registry/` URL in 6, `APP_ID_RE` in 5,\n * and `RegistryClientError` + `registryErrorJson` existed byte-identical in\n * two workers. Copies that must agree and nothing enforcing it.\n *\n * This module owns the MECHANICS: instance name, stub lookup, the wire call,\n * the error type, and the standard route mapping. Each worker still declares\n * its own typed convenience wrapper listing only the actions it uses — that\n * subset is genuinely per-worker; the plumbing is not.\n */\n\n/** The singleton registry instance — one DO holds every app/route/collaborator. */\nexport const REGISTRY_INSTANCE = 'v1:global'\n\n/** Public app ids have one canonical ULID-minted shape. */\nexport const APP_ID_RE = /^app_[0-9A-HJKMNP-TV-Z]{26}$/\nexport const STRICT_APP_ID_RE = APP_ID_RE\n\n/** Internal physical locators survive identity migration and are not public ids. */\nexport const RESOURCE_ID_RE = /^(app_[0-9A-HJKMNP-TV-Z]{26}|[a-z0-9][a-z0-9_-]{0,63})$/\n\nconst CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'\n\n/** A 26-char ULID (48-bit ms timestamp + 80 random bits, Crockford base32) —\n * the id shape shared by app ids (`app_…`) and workspace ids (`ws_…`). */\nexport function mintUlid(now = Date.now()): string {\n let ts = ''\n let t = now\n for (let i = 0; i < 10; i++) {\n ts = CROCKFORD[t % 32] + ts\n t = Math.floor(t / 32)\n }\n const rand = new Uint8Array(10)\n crypto.getRandomValues(rand)\n let rs = ''\n let acc = 0\n let bits = 0\n for (const byte of rand) {\n acc = (acc << 8) | byte\n bits += 8\n while (bits >= 5) {\n bits -= 5\n rs += CROCKFORD[(acc >> bits) & 31]\n }\n }\n return `${ts}${rs}`.slice(0, 26)\n}\n\n/** Mint a fresh app id: `app_` + 26-char ULID. App ids are minted ONLY by the\n * deploy worker's authenticated `POST /api/apps/mint`, which registers the id\n * to its caller in the same step — an id with no owner never exists. */\nexport function mintAppId(now = Date.now()): string {\n return `app_${mintUlid(now)}`\n}\n\nexport class RegistryClientError extends Error {\n constructor(\n message: string,\n readonly code: string,\n readonly status: number,\n ) {\n super(message)\n this.name = 'RegistryClientError'\n }\n}\n\nexport function registryStub(ns: DurableObjectNamespace): DurableObjectStub {\n return ns.get(ns.idFromName(REGISTRY_INSTANCE))\n}\n\n/**\n * One registry action over the wire. Non-2xx answers throw\n * {@link RegistryClientError} carrying the DO's structured `code` (or\n * `'error'` for a transport-shaped body) — callers distinguish a registry-\n * REPORTED miss (`code === 'not_found'`) from an outage by that code, never\n * by treating every throw as \"not found\".\n */\nexport async function callRegistry<T>(\n stub: DurableObjectStub,\n body: { action: string } & Record<string, unknown>,\n): Promise<T> {\n const res = await stub.fetch('https://registry/', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n const json = (await res.json().catch(() => ({}))) as { error?: string; code?: string }\n if (!res.ok) {\n throw new RegistryClientError(\n json.error ?? `Registry error (${res.status})`,\n json.code ?? 'error',\n res.status,\n )\n }\n return json as T\n}\n\n/** Standard mapping of a registry failure onto a route response. */\nexport function registryErrorJson(\n c: { json: (body: unknown, status?: number) => Response },\n err: unknown,\n): Response {\n if (err instanceof RegistryClientError) {\n return c.json({ error: err.message, code: err.code }, err.status as 400)\n }\n throw err\n}\n","/**\n * `deepspaceBuild()` — the one Vite plugin an app's build config needs.\n *\n * It carries the build-time wiring that is identical in every DeepSpace app\n * and belongs to the SDK's contract rather than to app code:\n *\n * 1. the app id `define` (see ./app-id) — so the browser is keyed to the\n * environment THIS build targets, never a literal frozen at scaffold time;\n * 2. removing the preview `.dev.vars` the Cloudflare plugin drops beside the\n * built worker — DeepSpace has one local runtime, so that second plaintext\n * copy is not kept;\n * 3. the client `dedupe` hint — two copies of React (or better-auth) break\n * the SDK's hooks, and which packages must be single-instance is the SDK's\n * knowledge, not the app's.\n *\n * Housing these here means a future fix to any of them ships in an SDK version\n * bump, with no edit to — and no migration of — a single app.\n *\n * The return is a structural Vite plugin: typing it against `vite` would make\n * the SDK carry a Vite dependency it otherwise does not need, and Vite's\n * plugin hooks are optional and method-shaped, so a compatible object is\n * assignable to `PluginOption` where an app composes it.\n */\n\nimport { existsSync, readdirSync, unlinkSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { appIdDefine } from './app-id'\nimport type { ResolveAppIdOptions } from './app-id'\n\n/**\n * Packages that must resolve to a single instance in the client graph. Two\n * Reacts throw \"Cannot read properties of null (reading 'useState')\"; two\n * better-auth clients split the session. The SDK owns this list because it is\n * the SDK's hooks and auth client that break when it is wrong.\n */\nexport const CLIENT_DEDUPE: readonly string[] = ['react', 'react-dom', 'better-auth']\n\n/** The subset of Vite's `Plugin` this helper implements — declared locally so\n * `deepspace/build` needs no `vite` dependency. Assignable to `PluginOption`. */\ninterface VitePluginLike {\n name: string\n enforce?: 'pre' | 'post'\n config(): { define: Record<string, string>; resolve: { dedupe: string[] } }\n closeBundle(): void\n}\n\nexport type DeepspaceBuildOptions = ResolveAppIdOptions\n\nexport function deepspaceBuild(options: DeepspaceBuildOptions): VitePluginLike {\n const distDir = resolve(options.appDir, 'dist')\n return {\n name: 'deepspace-build',\n enforce: 'post',\n config() {\n return {\n define: appIdDefine(options),\n resolve: { dedupe: [...CLIENT_DEDUPE] },\n }\n },\n closeBundle() {\n // Cloudflare emits a preview-only `.dev.vars` beside each built worker.\n if (!existsSync(distDir)) return\n for (const entry of readdirSync(distDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue\n const secretPath = join(distDir, entry.name, '.dev.vars')\n if (existsSync(secretPath)) unlinkSync(secretPath)\n }\n },\n }\n}\n"],"mappings":";AAqBA,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AACxB,SAAS,SAAS,iBAAiB;;;ACJ5B,IAAM,YAAY;;;ADiDlB,SAAS,aAAa,EAAE,QAAQ,MAAM,QAAQ,IAAI,GAAgC;AACvF,QAAM,aAAa,IAAI,uCACnB,QAAQ,IAAI,oCAAoC,IAChD,QAAQ,QAAQ,eAAe;AACnC,QAAM,UAAU,IAAI;AACpB,QAAM,UAAU,UAAU,QAAQ,OAAO,WAAW;AAEpD,MAAI;AACJ,MAAI;AACF,aAAS,UAAU,aAAa,YAAY,OAAO,CAAC;AAAA,EACtD,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,8BAA8B,UAAU,kCAClC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,IAC9D;AAAA,EACF;AAEA,QAAM,SAAS,UAAU,OAAO,MAAM,OAAO,GAAG,OAAO,OAAO,OAAO;AACrE,MAAI,OAAO,UAAU,YAAY,CAAC,UAAU,KAAK,KAAK,GAAG;AAIvD,UAAM,gBAAgB,UAAU,UAAa,UAAU;AACvD,UAAM,IAAI;AAAA,MACR,GAAG,UAAU,KAAK,OAAO,yCACb,UAAU,SAAY,YAAY,KAAK,UAAU,KAAK,CAAC,gHAEhE,gBACG,+BAA+B,UAAU,UAAU,OAAO,KAAK,EAAE,gFACjE,2DAA2D,OAAO,QACrE,UAAU,4EAA4E;AAAA,IAC3F;AAAA,EACF;AACA,SAAO;AACT;AAcO,SAAS,YAAY,SAAsD;AAChF,SAAO,EAAE,sBAAsB,KAAK,UAAU,aAAa,OAAO,CAAC,EAAE;AACvE;;;AE9FA,SAAS,YAAY,aAAa,kBAAkB;AACpD,SAAS,MAAM,WAAAA,gBAAe;AAUvB,IAAM,gBAAmC,CAAC,SAAS,aAAa,aAAa;AAa7E,SAAS,eAAe,SAAgD;AAC7E,QAAM,UAAUC,SAAQ,QAAQ,QAAQ,MAAM;AAC9C,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS;AACP,aAAO;AAAA,QACL,QAAQ,YAAY,OAAO;AAAA,QAC3B,SAAS,EAAE,QAAQ,CAAC,GAAG,aAAa,EAAE;AAAA,MACxC;AAAA,IACF;AAAA,IACA,cAAc;AAEZ,UAAI,CAAC,WAAW,OAAO,EAAG;AAC1B,iBAAW,SAAS,YAAY,SAAS,EAAE,eAAe,KAAK,CAAC,GAAG;AACjE,YAAI,CAAC,MAAM,YAAY,EAAG;AAC1B,cAAM,aAAa,KAAK,SAAS,MAAM,MAAM,WAAW;AACxD,YAAI,WAAW,UAAU,EAAG,YAAW,UAAU;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AACF;","names":["resolve","resolve"]}
@@ -1274,7 +1274,7 @@ function renderInlineMarkdown(value) {
1274
1274
  import { useEffect as useEffect6, useRef as useRef5, useState as useState5 } from "react";
1275
1275
 
1276
1276
  // package.json
1277
- var version = "0.22.1";
1277
+ var version = "0.23.0";
1278
1278
 
1279
1279
  // src/documentation/runtime/navigation.tsx
1280
1280
  import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
@@ -1954,4 +1954,4 @@ export {
1954
1954
  DefaultDocumentation,
1955
1955
  DocumentationContent
1956
1956
  };
1957
- //# sourceMappingURL=chunk-XXOLIJB5.js.map
1957
+ //# sourceMappingURL=chunk-A7VKZRCB.js.map