deepspace 0.10.0 → 0.10.2

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 (82) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/{chunk-DQ4G6VL6.js → chunk-T7V4GUIN.js} +283 -180
  3. package/dist/chunk-T7V4GUIN.js.map +1 -0
  4. package/dist/cli.js +1010 -6123
  5. package/dist/cli.js.map +1 -1
  6. package/dist/documentation-client-core.d.ts +18 -0
  7. package/dist/{docs-client-core.js → documentation-client-core.js} +60 -51
  8. package/dist/documentation-client-core.js.map +1 -0
  9. package/dist/documentation-default-renderer.cjs +124 -0
  10. package/dist/documentation-react.d.ts +2 -0
  11. package/dist/{docs-react.js → documentation-react.js} +8 -8
  12. package/dist/documentation-runtime.js +71 -0
  13. package/dist/documentation-server-core.d.ts +11 -0
  14. package/dist/{docs-server-core.js → documentation-server-core.js} +13 -13
  15. package/dist/documentation-server-core.js.map +1 -0
  16. package/dist/documentation.d.ts +366 -0
  17. package/dist/documentation.js +3953 -0
  18. package/dist/documentation.js.map +1 -0
  19. package/dist/feature-installer.js +145 -0
  20. package/dist/feature-installer.js.map +1 -1
  21. package/dist/index.d.ts +6 -8
  22. package/dist/index.js +5 -15
  23. package/dist/index.js.map +1 -1
  24. package/dist/public-BGRX_Cjd.d.ts +234 -0
  25. package/dist/schema.d.ts +186 -0
  26. package/dist/schema.js +610 -0
  27. package/dist/schema.js.map +1 -0
  28. package/dist/server.d.ts +19 -167
  29. package/dist/server.js +76 -1069
  30. package/dist/server.js.map +1 -1
  31. package/dist/testing.d.ts +5 -8
  32. package/dist/testing.js +48 -14
  33. package/dist/testing.js.map +1 -1
  34. package/dist/worker.d.ts +52 -173
  35. package/dist/worker.js +563 -492
  36. package/dist/worker.js.map +1 -1
  37. package/features/admin-page/src/admin-schema.ts +1 -1
  38. package/features/ai-chat/feature.json +2 -2
  39. package/features/ai-chat/src/ChatPanel.tsx +0 -1
  40. package/features/ai-chat/src/ai-chat-schema.ts +1 -2
  41. package/features/canvas/src/canvas-schema.ts +1 -1
  42. package/features/documentation/content/index.mdx +14 -0
  43. package/features/documentation/content/quickstart.mdx +12 -0
  44. package/features/documentation/documentation.json +18 -0
  45. package/features/documentation/feature.json +68 -0
  46. package/features/{docs → documents}/feature.json +27 -27
  47. package/features/{docs/src/DocsEditorPage.tsx → documents/src/DocumentsEditorPage.tsx} +74 -74
  48. package/features/{docs/src/DocsListPage.tsx → documents/src/DocumentsListPage.tsx} +102 -102
  49. package/features/{docs/src/DocsOutlinePanel.tsx → documents/src/DocumentsOutlinePanel.tsx} +9 -9
  50. package/features/{docs/src/DocsPresence.tsx → documents/src/DocumentsPresence.tsx} +18 -18
  51. package/features/{docs → documents}/src/InviteDialog.tsx +21 -21
  52. package/features/{docs → documents}/src/LibrarySidebar.tsx +25 -25
  53. package/features/documents/src/__tests__/documents-presence-access.test.ts +71 -0
  54. package/features/{docs/src/docs-library-types.ts → documents/src/documents-library-types.ts} +3 -3
  55. package/features/{docs/src/docs-schema.ts → documents/src/documents-schema.ts} +6 -6
  56. package/features/{docs/src/docs-ui.css → documents/src/documents-ui.css} +89 -89
  57. package/features/{docs → documents}/src/editor/DocEditorSurface.tsx +11 -11
  58. package/features/{docs/src/editor/DocsTiptapToolbar.tsx → documents/src/editor/DocumentsTiptapToolbar.tsx} +21 -21
  59. package/features/{docs → documents}/src/editor/useDocEditor.ts +4 -4
  60. package/features/{docs/src/use-docs-presence-access.ts → documents/src/use-documents-presence-access.ts} +51 -51
  61. package/features/file-attachments/src/attachments-schema.ts +1 -1
  62. package/features/items/src/items-schema.ts +1 -1
  63. package/features/leaderboard/src/leaderboard-schema.ts +1 -1
  64. package/features/messaging/src/ChatMultiPage.tsx +1 -1
  65. package/features/messaging/src/ChatPage.tsx +1 -1
  66. package/features/messaging/src/messaging-schema.ts +2 -2
  67. package/features/tasks/src/tasks-schema.ts +1 -1
  68. package/features/testing/src/test-schema.ts +2 -2
  69. package/package.json +12 -7
  70. package/dist/chunk-DQ4G6VL6.js.map +0 -1
  71. package/dist/docs-client-core.d.ts +0 -18
  72. package/dist/docs-client-core.js.map +0 -1
  73. package/dist/docs-react.d.ts +0 -2
  74. package/dist/docs-runtime.js +0 -71
  75. package/dist/docs-server-core.d.ts +0 -11
  76. package/dist/docs-server-core.js.map +0 -1
  77. package/dist/docs.d.ts +0 -350
  78. package/dist/docs.js +0 -5139
  79. package/dist/docs.js.map +0 -1
  80. package/dist/public-Cp1X8ZQ3.d.ts +0 -230
  81. package/features/docs/src/__tests__/docs-presence-access.test.ts +0 -71
  82. /package/dist/{docs-react.js.map → documentation-react.js.map} +0 -0
package/dist/testing.d.ts CHANGED
@@ -26,9 +26,10 @@ declare const test: _playwright_test.TestType<_playwright_test.PlaywrightTestArg
26
26
  /**
27
27
  * Account-credential plumbing for the testing module.
28
28
  *
29
- * Reads test accounts from `~/.deepspace/test-accounts.json` (the same
30
- * file `deepspace test accounts create` writes). Each account has at
31
- * minimum `{email, password}`; `name` and `label` are optional.
29
+ * Reads test accounts from the auth-origin-scoped registry at
30
+ * `~/.deepspace/test-accounts.json` (the same file
31
+ * `deepspace test accounts create` writes). Each account has at minimum
32
+ * `{email, password}`; `name` and `label` are optional.
32
33
  *
33
34
  * Used by the multi-user Playwright fixture in `./fixtures.ts` and is
34
35
  * also exported standalone in case suites want to do their own
@@ -43,11 +44,7 @@ interface TestAccount {
43
44
  userId?: string;
44
45
  createdAt?: number;
45
46
  }
46
- /**
47
- * Load all test accounts the developer has created. Returns an empty
48
- * array if the file doesn't exist yet.
49
- */
50
- declare function loadAllTestAccounts(): TestAccount[];
47
+ declare function loadAllTestAccounts(scope?: string): TestAccount[];
51
48
  /**
52
49
  * Pick `count` test accounts from the local registry, optionally
53
50
  * filtered by `label`. Order-stable: sorted by `createdAt` ascending so
package/dist/testing.js CHANGED
@@ -2,21 +2,55 @@
2
2
  import { test as baseTest } from "@playwright/test";
3
3
 
4
4
  // src/testing/accounts.ts
5
- import { existsSync, readFileSync } from "fs";
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
6
6
  import { homedir } from "os";
7
7
  import { join } from "path";
8
- var ACCOUNTS_PATH = join(homedir(), ".deepspace", "test-accounts.json");
9
- function loadAllTestAccounts() {
10
- if (!existsSync(ACCOUNTS_PATH)) return [];
8
+ var TEST_ACCOUNTS_PATH = join(homedir(), ".deepspace", "test-accounts.json");
9
+ var PRODUCTION_AUTH_ORIGIN = "https://auth.deep.space";
10
+ var STAGING_AUTH_ORIGIN = "https://auth.deepspacesites.com";
11
+ function currentTestAccountScope(env = process.env) {
12
+ const selected = env.DEEPSPACE_AUTH_URL ?? (env.DEEPSPACE_ENV === "staging" ? STAGING_AUTH_ORIGIN : PRODUCTION_AUTH_ORIGIN);
13
+ return normalizeTestAccountScope(selected);
14
+ }
15
+ function normalizeTestAccountScope(value) {
11
16
  try {
12
- const raw = JSON.parse(readFileSync(ACCOUNTS_PATH, "utf-8"));
13
- if (!Array.isArray(raw)) return [];
14
- return raw.filter(
15
- (r) => r && typeof r.email === "string" && typeof r.password === "string"
16
- );
17
+ return new URL(value).origin;
17
18
  } catch {
18
- return [];
19
+ return value;
20
+ }
21
+ }
22
+ function loadAllTestAccounts(scope = currentTestAccountScope()) {
23
+ const store = readTestAccountStore();
24
+ return mergeTestAccounts(store.scopes[scope] ?? [], store.unscoped);
25
+ }
26
+ function readTestAccountStore() {
27
+ if (!existsSync(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {}, unscoped: [] };
28
+ try {
29
+ const raw = JSON.parse(readFileSync(TEST_ACCOUNTS_PATH, "utf-8"));
30
+ if (Array.isArray(raw)) return { version: 2, scopes: {}, unscoped: validTestAccounts(raw) };
31
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
32
+ return { version: 2, scopes: {}, unscoped: [] };
33
+ }
34
+ const candidate = raw;
35
+ const scopes = candidate.scopes && typeof candidate.scopes === "object" && !Array.isArray(candidate.scopes) ? Object.fromEntries(Object.entries(candidate.scopes).map(([key, value]) => [key, validTestAccounts(value)])) : {};
36
+ return { version: 2, scopes, unscoped: validTestAccounts(candidate.unscoped) };
37
+ } catch {
38
+ return { version: 2, scopes: {}, unscoped: [] };
39
+ }
40
+ }
41
+ function validTestAccounts(value) {
42
+ if (!Array.isArray(value)) return [];
43
+ return value.filter((item) => Boolean(
44
+ item && typeof item === "object" && typeof item.email === "string" && typeof item.password === "string"
45
+ ));
46
+ }
47
+ function mergeTestAccounts(...groups) {
48
+ const accounts = /* @__PURE__ */ new Map();
49
+ for (const account of groups.flat()) {
50
+ const key = account.id ? `id:${account.id}` : `email:${account.email}`;
51
+ if (!accounts.has(key)) accounts.set(key, account);
19
52
  }
53
+ return [...accounts.values()];
20
54
  }
21
55
  function pickTestAccounts(count, options) {
22
56
  const all = loadAllTestAccounts();
@@ -25,7 +59,7 @@ function pickTestAccounts(count, options) {
25
59
  if (sorted.length < count) {
26
60
  const labelHint = options?.label ? ` (label="${options.label}")` : "";
27
61
  throw new Error(
28
- `Multiplayer test needs ${count} test accounts${labelHint}, but only ${sorted.length} are present in ${ACCOUNTS_PATH}. Create more with:
62
+ `Multiplayer test needs ${count} test accounts${labelHint}, but only ${sorted.length} are present in ${TEST_ACCOUNTS_PATH}. Create more with:
29
63
  deepspace test accounts create --email <name>@deepspace.test --password <pw> --name "<name>"${options?.label ? ` --label ${options.label}` : ""}`
30
64
  );
31
65
  }
@@ -36,7 +70,7 @@ function findTestAccountByName(name) {
36
70
  const match = all.find((a) => a.name === name);
37
71
  if (!match) {
38
72
  throw new Error(
39
- `No test account named "${name}" in ${ACCOUNTS_PATH}. Create with:
73
+ `No test account named "${name}" in ${TEST_ACCOUNTS_PATH}. Create with:
40
74
  deepspace test accounts create --email ${name.toLowerCase()}@deepspace.test --password <pw> --name "${name}"`
41
75
  );
42
76
  }
@@ -45,7 +79,7 @@ function findTestAccountByName(name) {
45
79
 
46
80
  // src/testing/storage-state.ts
47
81
  import { createHash } from "crypto";
48
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, statSync } from "fs";
82
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, statSync } from "fs";
49
83
  import { homedir as homedir2 } from "os";
50
84
  import { join as join2 } from "path";
51
85
  var STATES_DIR = join2(homedir2(), ".deepspace", "playwright-states");
@@ -55,7 +89,7 @@ function statePathForEmail(email) {
55
89
  return join2(STATES_DIR, `${hash}.json`);
56
90
  }
57
91
  function ensureStatesDir() {
58
- if (!existsSync2(STATES_DIR)) mkdirSync(STATES_DIR, { recursive: true });
92
+ if (!existsSync2(STATES_DIR)) mkdirSync2(STATES_DIR, { recursive: true });
59
93
  }
60
94
  function isCacheFresh(path, maxAgeMs) {
61
95
  if (!existsSync2(path)) return false;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/testing/fixtures.ts","../src/testing/accounts.ts","../src/testing/storage-state.ts"],"sourcesContent":["/**\n * Multi-user Playwright fixture for DeepSpace apps.\n *\n * Usage:\n *\n * import { test, expect } from 'deepspace/testing'\n *\n * test('A sends, B sees', async ({ users }) => {\n * const [a, b] = await users(2)\n * await a.page.goto('/chat')\n * await b.page.goto('/chat')\n * await a.page.getByTestId('message-input').fill('hi')\n * await a.page.getByTestId('send-message-btn').click()\n * await expect(b.page.getByText('hi')).toBeVisible()\n * })\n *\n * `users(N)` picks the first N test accounts from\n * `~/.deepspace/test-accounts.json` (created via\n * `deepspace test accounts create`). `users(['Alice', 'Bob'])` picks\n * specific accounts by name.\n *\n * Each user gets a separate browser context with cached storageState\n * (per-account JSON in `~/.deepspace/playwright-states/`). Sign-in\n * happens once per account, not once per test, sidestepping Better\n * Auth's per-IP rate limit on `/api/auth/sign-in/email`.\n *\n * Contexts are auto-closed when the test finishes.\n */\n\nimport { test as baseTest, type Browser, type BrowserContext, type Page } from '@playwright/test'\nimport {\n type TestAccount,\n pickTestAccounts,\n findTestAccountByName,\n} from './accounts'\nimport { ensureStorageState } from './storage-state'\n\nexport interface MultiplayerUser {\n context: BrowserContext\n page: Page\n email: string\n name: string\n /** Test account user ID, if known from the accounts registry. */\n userId?: string\n}\n\n/**\n * Fixture function signature. Pass either a count (`users(2)`) or an\n * array of account names (`users(['Alice', 'Bob'])`). Returns one entry\n * per requested user.\n */\nexport type UsersFixture = (\n selector: number | string[],\n options?: { label?: string },\n) => Promise<MultiplayerUser[]>\n\ninterface MultiplayerFixtures {\n users: UsersFixture\n}\n\nasync function buildUser(\n browser: Browser,\n account: TestAccount,\n baseURL: string,\n): Promise<{ user: MultiplayerUser; cleanup: () => Promise<void> }> {\n const statePath = await ensureStorageState(browser, account, baseURL)\n const context = await browser.newContext({ storageState: statePath, baseURL })\n const page = await context.newPage()\n return {\n user: {\n context,\n page,\n email: account.email,\n name: account.name ?? account.email.split('@')[0],\n userId: account.userId,\n },\n cleanup: async () => {\n await context.close()\n },\n }\n}\n\nexport const test = baseTest.extend<MultiplayerFixtures>({\n users: async ({ browser, baseURL }, use) => {\n if (!baseURL) {\n throw new Error(\n '`users` fixture requires a baseURL. Set it on your Playwright config ' +\n '(e.g. `use: { baseURL: \"http://localhost:5173\" }`) or via the ' +\n 'webServer.url shortcut.',\n )\n }\n\n const cleanups: Array<() => Promise<void>> = []\n\n const usersFn: UsersFixture = async (selector, options) => {\n const accounts: TestAccount[] = Array.isArray(selector)\n ? selector.map((name) => findTestAccountByName(name))\n : pickTestAccounts(selector, options)\n\n const built = await Promise.all(\n accounts.map((acct) => buildUser(browser, acct, baseURL)),\n )\n for (const b of built) cleanups.push(b.cleanup)\n return built.map((b) => b.user)\n }\n\n await use(usersFn)\n\n // Tear down any contexts we created. Errors are swallowed — a test\n // failure shouldn't be masked by a cleanup error.\n for (const cleanup of cleanups) {\n await cleanup().catch(() => {})\n }\n },\n})\n\nexport { expect } from '@playwright/test'\n","/**\n * Account-credential plumbing for the testing module.\n *\n * Reads test accounts from `~/.deepspace/test-accounts.json` (the same\n * file `deepspace test accounts create` writes). Each account has at\n * minimum `{email, password}`; `name` and `label` are optional.\n *\n * Used by the multi-user Playwright fixture in `./fixtures.ts` and is\n * also exported standalone in case suites want to do their own\n * orchestration.\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nexport interface TestAccount {\n email: string\n password: string\n name?: string\n label?: string | null\n id?: string\n userId?: string\n createdAt?: number\n}\n\nconst ACCOUNTS_PATH = join(homedir(), '.deepspace', 'test-accounts.json')\n\n/**\n * Load all test accounts the developer has created. Returns an empty\n * array if the file doesn't exist yet.\n */\nexport function loadAllTestAccounts(): TestAccount[] {\n if (!existsSync(ACCOUNTS_PATH)) return []\n try {\n const raw = JSON.parse(readFileSync(ACCOUNTS_PATH, 'utf-8'))\n if (!Array.isArray(raw)) return []\n // Filter out any malformed entries (missing email or password). Keep\n // bare {email, password} rows so the file format stays forgiving.\n return raw.filter(\n (r) => r && typeof r.email === 'string' && typeof r.password === 'string',\n )\n } catch {\n return []\n }\n}\n\n/**\n * Pick `count` test accounts from the local registry, optionally\n * filtered by `label`. Order-stable: sorted by `createdAt` ascending so\n * the first test account you created is always the first one returned.\n *\n * Throws with a helpful message if not enough accounts exist.\n */\nexport function pickTestAccounts(\n count: number,\n options?: { label?: string },\n): TestAccount[] {\n const all = loadAllTestAccounts()\n const filtered = options?.label\n ? all.filter((a) => a.label === options.label)\n : all\n const sorted = filtered.slice().sort((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0))\n\n if (sorted.length < count) {\n const labelHint = options?.label ? ` (label=\"${options.label}\")` : ''\n throw new Error(\n `Multiplayer test needs ${count} test accounts${labelHint}, but only ${sorted.length} ` +\n `are present in ${ACCOUNTS_PATH}. Create more with:\\n` +\n ` deepspace test accounts create --email <name>@deepspace.test --password <pw> --name \"<name>\"${\n options?.label ? ` --label ${options.label}` : ''\n }`,\n )\n }\n\n return sorted.slice(0, count)\n}\n\n/**\n * Find a single account by name. Used by `users(['Alice', 'Bob'])`.\n * Throws if not found.\n */\nexport function findTestAccountByName(name: string): TestAccount {\n const all = loadAllTestAccounts()\n const match = all.find((a) => a.name === name)\n if (!match) {\n throw new Error(\n `No test account named \"${name}\" in ${ACCOUNTS_PATH}. Create with:\\n` +\n ` deepspace test accounts create --email ${name.toLowerCase()}@deepspace.test --password <pw> --name \"${name}\"`,\n )\n }\n return match\n}\n","/**\n * Per-account `storageState` cache for Playwright.\n *\n * Better Auth applies a per-IP rate limit on `/api/auth/sign-in/email`\n * (~5 sign-ins / 60s / endpoint). A multiplayer suite that spins up\n * 5+ users per spec would hit the limit and start failing in non-obvious\n * ways. This module signs each account in *once*, persists the\n * resulting browser cookies + storage to disk, and reuses the file on\n * subsequent runs.\n *\n * Cache layout:\n * ~/.deepspace/playwright-states/<sha256(email)>.json\n *\n * Validity: a cached file is reused if Playwright successfully loads it\n * AND the resulting context produces a non-anonymous session on the\n * target app. If validation fails, we fall back to a fresh sign-in and\n * overwrite the cache.\n */\n\nimport { createHash } from 'node:crypto'\nimport { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { Browser, BrowserContext } from '@playwright/test'\n\nconst STATES_DIR = join(homedir(), '.deepspace', 'playwright-states')\n\n/** Default freshness window — re-sign-in if the cache is older than this. */\nconst DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000 // 7 days\n\nfunction statePathForEmail(email: string): string {\n const hash = createHash('sha256').update(email.trim().toLowerCase()).digest('hex').slice(0, 24)\n return join(STATES_DIR, `${hash}.json`)\n}\n\nfunction ensureStatesDir() {\n if (!existsSync(STATES_DIR)) mkdirSync(STATES_DIR, { recursive: true })\n}\n\n/** Returns true if a cache file exists and is younger than `maxAgeMs`. */\nfunction isCacheFresh(path: string, maxAgeMs: number): boolean {\n if (!existsSync(path)) return false\n try {\n const age = Date.now() - statSync(path).mtimeMs\n return age < maxAgeMs\n } catch {\n return false\n }\n}\n\n/**\n * Sign `account` in by POSTing `/api/auth/sign-in/email` against\n * `baseURL` and capturing the resulting cookies. Returns the path to the\n * Playwright `storageState` JSON file.\n *\n * The browser-driven approach is intentional: we want the cookies set\n * on the same origin Playwright contexts will load from, otherwise the\n * Set-Cookie domain attribute won't match.\n */\nasync function signInAndSaveState(\n browser: Browser,\n account: { email: string; password: string },\n baseURL: string,\n outPath: string,\n): Promise<string> {\n ensureStatesDir()\n const ctx = await browser.newContext({ baseURL })\n try {\n const page = await ctx.newPage()\n // Hit the app first so the origin is established (some auth plugins\n // require a same-origin Origin header on sign-in).\n await page.goto('/')\n const ok = await page.evaluate(\n async ({ email, password }: { email: string; password: string }) => {\n const res = await fetch('/api/auth/sign-in/email', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email, password }),\n })\n return res.ok\n },\n account,\n )\n if (!ok) {\n throw new Error(\n `Sign-in failed for ${account.email}. Check the password is current; ` +\n `if the account was deleted on the server, re-create with ` +\n `\\`deepspace test accounts create\\`.`,\n )\n }\n await ctx.storageState({ path: outPath })\n return outPath\n } finally {\n await ctx.close()\n }\n}\n\nexport interface EnsureStorageStateOptions {\n /** Max age of a cached state file before we re-sign-in. Default 7 days. */\n maxAgeMs?: number\n /** Force a fresh sign-in even if the cache is fresh. */\n force?: boolean\n}\n\n/**\n * Ensure a Playwright `storageState` file exists for `account` and\n * return its path. Signs in once if the cache is missing, stale, or\n * `force: true`.\n */\nexport async function ensureStorageState(\n browser: Browser,\n account: { email: string; password: string },\n baseURL: string,\n options: EnsureStorageStateOptions = {},\n): Promise<string> {\n const path = statePathForEmail(account.email)\n const maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS\n\n if (!options.force && isCacheFresh(path, maxAgeMs)) {\n return path\n }\n\n return signInAndSaveState(browser, account, baseURL, path)\n}\n\n/**\n * Convenience: open a fresh `BrowserContext` for `account` using the\n * cached storage state (signing in if needed). The caller is responsible\n * for closing the context.\n */\nexport async function newSignedInContext(\n browser: Browser,\n account: { email: string; password: string },\n baseURL: string,\n options: EnsureStorageStateOptions = {},\n): Promise<BrowserContext> {\n const statePath = await ensureStorageState(browser, account, baseURL, options)\n return browser.newContext({ storageState: statePath, baseURL })\n}\n\n/** Read a cached storageState file (mostly for debugging). */\nexport function readCachedState(email: string): unknown | null {\n const path = statePathForEmail(email)\n if (!existsSync(path)) return null\n try {\n return JSON.parse(readFileSync(path, 'utf-8'))\n } catch {\n return null\n }\n}\n\n/** Where the cache for `email` lives. Useful for diagnostics. */\nexport function getStatePathForEmail(email: string): string {\n return statePathForEmail(email)\n}\n"],"mappings":";AA6BA,SAAS,QAAQ,gBAA8D;;;ACjB/E,SAAS,YAAY,oBAAoB;AACzC,SAAS,eAAe;AACxB,SAAS,YAAY;AAYrB,IAAM,gBAAgB,KAAK,QAAQ,GAAG,cAAc,oBAAoB;AAMjE,SAAS,sBAAqC;AACnD,MAAI,CAAC,WAAW,aAAa,EAAG,QAAO,CAAC;AACxC,MAAI;AACF,UAAM,MAAM,KAAK,MAAM,aAAa,eAAe,OAAO,CAAC;AAC3D,QAAI,CAAC,MAAM,QAAQ,GAAG,EAAG,QAAO,CAAC;AAGjC,WAAO,IAAI;AAAA,MACT,CAAC,MAAM,KAAK,OAAO,EAAE,UAAU,YAAY,OAAO,EAAE,aAAa;AAAA,IACnE;AAAA,EACF,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AASO,SAAS,iBACd,OACA,SACe;AACf,QAAM,MAAM,oBAAoB;AAChC,QAAM,WAAW,SAAS,QACtB,IAAI,OAAO,CAAC,MAAM,EAAE,UAAU,QAAQ,KAAK,IAC3C;AACJ,QAAM,SAAS,SAAS,MAAM,EAAE,KAAK,CAAC,GAAG,OAAO,EAAE,aAAa,MAAM,EAAE,aAAa,EAAE;AAEtF,MAAI,OAAO,SAAS,OAAO;AACzB,UAAM,YAAY,SAAS,QAAQ,YAAY,QAAQ,KAAK,OAAO;AACnE,UAAM,IAAI;AAAA,MACR,0BAA0B,KAAK,iBAAiB,SAAS,cAAc,OAAO,MAAM,mBAChE,aAAa;AAAA,gGAE7B,SAAS,QAAQ,YAAY,QAAQ,KAAK,KAAK,EACjD;AAAA,IACJ;AAAA,EACF;AAEA,SAAO,OAAO,MAAM,GAAG,KAAK;AAC9B;AAMO,SAAS,sBAAsB,MAA2B;AAC/D,QAAM,MAAM,oBAAoB;AAChC,QAAM,QAAQ,IAAI,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAC7C,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;AAAA,MACR,0BAA0B,IAAI,QAAQ,aAAa;AAAA,2CACL,KAAK,YAAY,CAAC,2CAA2C,IAAI;AAAA,IACjH;AAAA,EACF;AACA,SAAO;AACT;;;ACzEA,SAAS,kBAAkB;AAC3B,SAAS,cAAAA,aAAY,WAAW,gBAAAC,eAAc,gBAAgB;AAC9D,SAAS,WAAAC,gBAAe;AACxB,SAAS,QAAAC,aAAY;AAGrB,IAAM,aAAaA,MAAKD,SAAQ,GAAG,cAAc,mBAAmB;AAGpE,IAAM,qBAAqB,IAAI,KAAK,KAAK,KAAK;AAE9C,SAAS,kBAAkB,OAAuB;AAChD,QAAM,OAAO,WAAW,QAAQ,EAAE,OAAO,MAAM,KAAK,EAAE,YAAY,CAAC,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAC9F,SAAOC,MAAK,YAAY,GAAG,IAAI,OAAO;AACxC;AAEA,SAAS,kBAAkB;AACzB,MAAI,CAACH,YAAW,UAAU,EAAG,WAAU,YAAY,EAAE,WAAW,KAAK,CAAC;AACxE;AAGA,SAAS,aAAa,MAAc,UAA2B;AAC7D,MAAI,CAACA,YAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,UAAM,MAAM,KAAK,IAAI,IAAI,SAAS,IAAI,EAAE;AACxC,WAAO,MAAM;AAAA,EACf,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAWA,eAAe,mBACb,SACA,SACA,SACA,SACiB;AACjB,kBAAgB;AAChB,QAAM,MAAM,MAAM,QAAQ,WAAW,EAAE,QAAQ,CAAC;AAChD,MAAI;AACF,UAAM,OAAO,MAAM,IAAI,QAAQ;AAG/B,UAAM,KAAK,KAAK,GAAG;AACnB,UAAM,KAAK,MAAM,KAAK;AAAA,MACpB,OAAO,EAAE,OAAO,SAAS,MAA2C;AAClE,cAAM,MAAM,MAAM,MAAM,2BAA2B;AAAA,UACjD,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,SAAS,CAAC;AAAA,QAC1C,CAAC;AACD,eAAO,IAAI;AAAA,MACb;AAAA,MACA;AAAA,IACF;AACA,QAAI,CAAC,IAAI;AACP,YAAM,IAAI;AAAA,QACR,sBAAsB,QAAQ,KAAK;AAAA,MAGrC;AAAA,IACF;AACA,UAAM,IAAI,aAAa,EAAE,MAAM,QAAQ,CAAC;AACxC,WAAO;AAAA,EACT,UAAE;AACA,UAAM,IAAI,MAAM;AAAA,EAClB;AACF;AAcA,eAAsB,mBACpB,SACA,SACA,SACA,UAAqC,CAAC,GACrB;AACjB,QAAM,OAAO,kBAAkB,QAAQ,KAAK;AAC5C,QAAM,WAAW,QAAQ,YAAY;AAErC,MAAI,CAAC,QAAQ,SAAS,aAAa,MAAM,QAAQ,GAAG;AAClD,WAAO;AAAA,EACT;AAEA,SAAO,mBAAmB,SAAS,SAAS,SAAS,IAAI;AAC3D;AAOA,eAAsB,mBACpB,SACA,SACA,SACA,UAAqC,CAAC,GACb;AACzB,QAAM,YAAY,MAAM,mBAAmB,SAAS,SAAS,SAAS,OAAO;AAC7E,SAAO,QAAQ,WAAW,EAAE,cAAc,WAAW,QAAQ,CAAC;AAChE;AAGO,SAAS,gBAAgB,OAA+B;AAC7D,QAAM,OAAO,kBAAkB,KAAK;AACpC,MAAI,CAACA,YAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,WAAO,KAAK,MAAMC,cAAa,MAAM,OAAO,CAAC;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,qBAAqB,OAAuB;AAC1D,SAAO,kBAAkB,KAAK;AAChC;;;AFtCA,SAAS,cAAc;AAxDvB,eAAe,UACb,SACA,SACA,SACkE;AAClE,QAAM,YAAY,MAAM,mBAAmB,SAAS,SAAS,OAAO;AACpE,QAAM,UAAU,MAAM,QAAQ,WAAW,EAAE,cAAc,WAAW,QAAQ,CAAC;AAC7E,QAAM,OAAO,MAAM,QAAQ,QAAQ;AACnC,SAAO;AAAA,IACL,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AAAA,MACf,MAAM,QAAQ,QAAQ,QAAQ,MAAM,MAAM,GAAG,EAAE,CAAC;AAAA,MAChD,QAAQ,QAAQ;AAAA,IAClB;AAAA,IACA,SAAS,YAAY;AACnB,YAAM,QAAQ,MAAM;AAAA,IACtB;AAAA,EACF;AACF;AAEO,IAAM,OAAO,SAAS,OAA4B;AAAA,EACvD,OAAO,OAAO,EAAE,SAAS,QAAQ,GAAG,QAAQ;AAC1C,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI;AAAA,QACR;AAAA,MAGF;AAAA,IACF;AAEA,UAAM,WAAuC,CAAC;AAE9C,UAAM,UAAwB,OAAO,UAAU,YAAY;AACzD,YAAM,WAA0B,MAAM,QAAQ,QAAQ,IAClD,SAAS,IAAI,CAAC,SAAS,sBAAsB,IAAI,CAAC,IAClD,iBAAiB,UAAU,OAAO;AAEtC,YAAM,QAAQ,MAAM,QAAQ;AAAA,QAC1B,SAAS,IAAI,CAAC,SAAS,UAAU,SAAS,MAAM,OAAO,CAAC;AAAA,MAC1D;AACA,iBAAW,KAAK,MAAO,UAAS,KAAK,EAAE,OAAO;AAC9C,aAAO,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,IAChC;AAEA,UAAM,IAAI,OAAO;AAIjB,eAAW,WAAW,UAAU;AAC9B,YAAM,QAAQ,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAChC;AAAA,EACF;AACF,CAAC;","names":["existsSync","readFileSync","homedir","join"]}
1
+ {"version":3,"sources":["../src/testing/fixtures.ts","../src/testing/accounts.ts","../src/testing/storage-state.ts"],"sourcesContent":["/**\n * Multi-user Playwright fixture for DeepSpace apps.\n *\n * Usage:\n *\n * import { test, expect } from 'deepspace/testing'\n *\n * test('A sends, B sees', async ({ users }) => {\n * const [a, b] = await users(2)\n * await a.page.goto('/chat')\n * await b.page.goto('/chat')\n * await a.page.getByTestId('message-input').fill('hi')\n * await a.page.getByTestId('send-message-btn').click()\n * await expect(b.page.getByText('hi')).toBeVisible()\n * })\n *\n * `users(N)` picks the first N test accounts from\n * `~/.deepspace/test-accounts.json` (created via\n * `deepspace test accounts create`). `users(['Alice', 'Bob'])` picks\n * specific accounts by name.\n *\n * Each user gets a separate browser context with cached storageState\n * (per-account JSON in `~/.deepspace/playwright-states/`). Sign-in\n * happens once per account, not once per test, sidestepping Better\n * Auth's per-IP rate limit on `/api/auth/sign-in/email`.\n *\n * Contexts are auto-closed when the test finishes.\n */\n\nimport { test as baseTest, type Browser, type BrowserContext, type Page } from '@playwright/test'\nimport {\n type TestAccount,\n pickTestAccounts,\n findTestAccountByName,\n} from './accounts'\nimport { ensureStorageState } from './storage-state'\n\nexport interface MultiplayerUser {\n context: BrowserContext\n page: Page\n email: string\n name: string\n /** Test account user ID, if known from the accounts registry. */\n userId?: string\n}\n\n/**\n * Fixture function signature. Pass either a count (`users(2)`) or an\n * array of account names (`users(['Alice', 'Bob'])`). Returns one entry\n * per requested user.\n */\nexport type UsersFixture = (\n selector: number | string[],\n options?: { label?: string },\n) => Promise<MultiplayerUser[]>\n\ninterface MultiplayerFixtures {\n users: UsersFixture\n}\n\nasync function buildUser(\n browser: Browser,\n account: TestAccount,\n baseURL: string,\n): Promise<{ user: MultiplayerUser; cleanup: () => Promise<void> }> {\n const statePath = await ensureStorageState(browser, account, baseURL)\n const context = await browser.newContext({ storageState: statePath, baseURL })\n const page = await context.newPage()\n return {\n user: {\n context,\n page,\n email: account.email,\n name: account.name ?? account.email.split('@')[0],\n userId: account.userId,\n },\n cleanup: async () => {\n await context.close()\n },\n }\n}\n\nexport const test = baseTest.extend<MultiplayerFixtures>({\n users: async ({ browser, baseURL }, use) => {\n if (!baseURL) {\n throw new Error(\n '`users` fixture requires a baseURL. Set it on your Playwright config ' +\n '(e.g. `use: { baseURL: \"http://localhost:5173\" }`) or via the ' +\n 'webServer.url shortcut.',\n )\n }\n\n const cleanups: Array<() => Promise<void>> = []\n\n const usersFn: UsersFixture = async (selector, options) => {\n const accounts: TestAccount[] = Array.isArray(selector)\n ? selector.map((name) => findTestAccountByName(name))\n : pickTestAccounts(selector, options)\n\n const built = await Promise.all(\n accounts.map((acct) => buildUser(browser, acct, baseURL)),\n )\n for (const b of built) cleanups.push(b.cleanup)\n return built.map((b) => b.user)\n }\n\n await use(usersFn)\n\n // Tear down any contexts we created. Errors are swallowed — a test\n // failure shouldn't be masked by a cleanup error.\n for (const cleanup of cleanups) {\n await cleanup().catch(() => {})\n }\n },\n})\n\nexport { expect } from '@playwright/test'\n","/**\n * Account-credential plumbing for the testing module.\n *\n * Reads test accounts from the auth-origin-scoped registry at\n * `~/.deepspace/test-accounts.json` (the same file\n * `deepspace test accounts create` writes). Each account has at minimum\n * `{email, password}`; `name` and `label` are optional.\n *\n * Used by the multi-user Playwright fixture in `./fixtures.ts` and is\n * also exported standalone in case suites want to do their own\n * orchestration.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nexport interface TestAccount {\n email: string\n password: string\n name?: string\n label?: string | null\n id?: string\n userId?: string\n createdAt?: number\n}\n\nexport const TEST_ACCOUNTS_PATH = join(homedir(), '.deepspace', 'test-accounts.json')\n\nconst PRODUCTION_AUTH_ORIGIN = 'https://auth.deep.space'\nconst STAGING_AUTH_ORIGIN = 'https://auth.deepspacesites.com'\n\nexport interface TestAccountCredentialStore {\n version: 2\n scopes: Record<string, TestAccount[]>\n /** Legacy credentials stay here until a remote id proves their plane. */\n unscoped: TestAccount[]\n}\n\nexport interface RemoteTestAccount {\n id: string\n email: string\n userId: string\n label: string | null\n createdAt: number\n}\n\n/**\n * Load all test accounts the developer has created. Returns an empty\n * array if the file doesn't exist yet.\n */\nexport function currentTestAccountScope(env: NodeJS.ProcessEnv = process.env): string {\n const selected = env.DEEPSPACE_AUTH_URL ??\n (env.DEEPSPACE_ENV === 'staging' ? STAGING_AUTH_ORIGIN : PRODUCTION_AUTH_ORIGIN)\n return normalizeTestAccountScope(selected)\n}\n\nexport function normalizeTestAccountScope(value: string): string {\n try {\n return new URL(value).origin\n } catch {\n return value\n }\n}\n\nexport function loadAllTestAccounts(scope = currentTestAccountScope()): TestAccount[] {\n const store = readTestAccountStore()\n return mergeTestAccounts(store.scopes[scope] ?? [], store.unscoped)\n}\n\n/**\n * Replace the local credential registry with the subset that still exists\n * remotely. Passwords and display names remain local-only; a recreated email\n * with a different id is deliberately treated as a different account.\n */\nexport function reconcileTestAccountStore(\n remote: RemoteTestAccount[],\n scope = currentTestAccountScope(),\n): {\n accounts: TestAccount[]\n removed: number\n} {\n const result = reconcileTestAccountScopes(readTestAccountStore(), remote, scope)\n saveTestAccountStore(result.store)\n return { accounts: result.accounts, removed: result.removed }\n}\n\nexport function reconcileTestAccountScopes(\n store: TestAccountCredentialStore,\n remote: RemoteTestAccount[],\n scope: string,\n): { store: TestAccountCredentialStore; accounts: TestAccount[]; removed: number } {\n const scoped = store.scopes[scope] ?? []\n const current = reconcileTestAccounts(scoped, remote)\n const migrated = reconcileTestAccounts(store.unscoped, remote)\n const accounts = mergeTestAccounts(current, migrated)\n return {\n accounts,\n removed: Math.max(0, scoped.length - current.length),\n store: {\n version: 2,\n scopes: { ...store.scopes, [scope]: accounts },\n unscoped: store.unscoped.filter((account) => !remote.some((item) => accountMatchesRemote(account, item))),\n },\n }\n}\n\nexport function reconcileTestAccounts(\n local: TestAccount[],\n remote: RemoteTestAccount[],\n): TestAccount[] {\n const localById = new Map(local.filter((account) => account.id).map((account) => [account.id, account]))\n const legacyByEmail = new Map(local.filter((account) => !account.id).map((account) => [account.email, account]))\n return remote.flatMap((account) => {\n const stored = localById.get(account.id) ?? legacyByEmail.get(account.email)\n return stored ? [{ ...stored, ...account }] : []\n })\n}\n\n/** Add or replace one locally stored credential without creating duplicates. */\nexport function upsertTestAccount(account: TestAccount, scope = currentTestAccountScope()): void {\n const store = readTestAccountStore()\n const accounts = (store.scopes[scope] ?? []).filter(\n (stored) => stored.id !== account.id && stored.email !== account.email,\n )\n saveTestAccountStore({\n version: 2,\n scopes: { ...store.scopes, [scope]: [...accounts, account] },\n unscoped: store.unscoped.filter(\n (stored) => stored.id !== account.id && stored.email !== account.email,\n ),\n })\n}\n\n/** Remove credentials after the corresponding remote delete succeeds. */\nexport function removeTestAccounts(\n ids: string[],\n emails: string[] = [],\n scope = currentTestAccountScope(),\n): void {\n const idSet = new Set(ids)\n const emailSet = new Set(emails)\n const keep = (account: TestAccount): boolean =>\n !idSet.has(account.id ?? '') && !emailSet.has(account.email)\n const store = readTestAccountStore()\n saveTestAccountStore({\n version: 2,\n scopes: { ...store.scopes, [scope]: (store.scopes[scope] ?? []).filter(keep) },\n unscoped: store.unscoped.filter(keep),\n })\n}\n\nfunction readTestAccountStore(): TestAccountCredentialStore {\n if (!existsSync(TEST_ACCOUNTS_PATH)) return { version: 2, scopes: {}, unscoped: [] }\n try {\n const raw: unknown = JSON.parse(readFileSync(TEST_ACCOUNTS_PATH, 'utf-8'))\n if (Array.isArray(raw)) return { version: 2, scopes: {}, unscoped: validTestAccounts(raw) }\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n return { version: 2, scopes: {}, unscoped: [] }\n }\n const candidate = raw as { scopes?: unknown; unscoped?: unknown }\n const scopes = candidate.scopes && typeof candidate.scopes === 'object' && !Array.isArray(candidate.scopes)\n ? Object.fromEntries(Object.entries(candidate.scopes).map(([key, value]) => [key, validTestAccounts(value)]))\n : {}\n return { version: 2, scopes, unscoped: validTestAccounts(candidate.unscoped) }\n } catch {\n return { version: 2, scopes: {}, unscoped: [] }\n }\n}\n\nfunction validTestAccounts(value: unknown): TestAccount[] {\n if (!Array.isArray(value)) return []\n return value.filter((item): item is TestAccount => Boolean(\n item && typeof item === 'object' &&\n typeof (item as TestAccount).email === 'string' &&\n typeof (item as TestAccount).password === 'string',\n ))\n}\n\nfunction mergeTestAccounts(...groups: TestAccount[][]): TestAccount[] {\n const accounts = new Map<string, TestAccount>()\n for (const account of groups.flat()) {\n const key = account.id ? `id:${account.id}` : `email:${account.email}`\n if (!accounts.has(key)) accounts.set(key, account)\n }\n return [...accounts.values()]\n}\n\nfunction accountMatchesRemote(local: TestAccount, remote: RemoteTestAccount): boolean {\n return local.id ? local.id === remote.id : local.email === remote.email\n}\n\nfunction saveTestAccountStore(store: TestAccountCredentialStore): void {\n mkdirSync(join(homedir(), '.deepspace'), { recursive: true })\n writeFileSync(TEST_ACCOUNTS_PATH, JSON.stringify(store, null, 2), { mode: 0o600 })\n}\n\n/**\n * Pick `count` test accounts from the local registry, optionally\n * filtered by `label`. Order-stable: sorted by `createdAt` ascending so\n * the first test account you created is always the first one returned.\n *\n * Throws with a helpful message if not enough accounts exist.\n */\nexport function pickTestAccounts(\n count: number,\n options?: { label?: string },\n): TestAccount[] {\n const all = loadAllTestAccounts()\n const filtered = options?.label\n ? all.filter((a) => a.label === options.label)\n : all\n const sorted = filtered.slice().sort((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0))\n\n if (sorted.length < count) {\n const labelHint = options?.label ? ` (label=\"${options.label}\")` : ''\n throw new Error(\n `Multiplayer test needs ${count} test accounts${labelHint}, but only ${sorted.length} ` +\n `are present in ${TEST_ACCOUNTS_PATH}. Create more with:\\n` +\n ` deepspace test accounts create --email <name>@deepspace.test --password <pw> --name \"<name>\"${\n options?.label ? ` --label ${options.label}` : ''\n }`,\n )\n }\n\n return sorted.slice(0, count)\n}\n\n/**\n * Find a single account by name. Used by `users(['Alice', 'Bob'])`.\n * Throws if not found.\n */\nexport function findTestAccountByName(name: string): TestAccount {\n const all = loadAllTestAccounts()\n const match = all.find((a) => a.name === name)\n if (!match) {\n throw new Error(\n `No test account named \"${name}\" in ${TEST_ACCOUNTS_PATH}. Create with:\\n` +\n ` deepspace test accounts create --email ${name.toLowerCase()}@deepspace.test --password <pw> --name \"${name}\"`,\n )\n }\n return match\n}\n","/**\n * Per-account `storageState` cache for Playwright.\n *\n * Better Auth applies a per-IP rate limit on `/api/auth/sign-in/email`\n * (~5 sign-ins / 60s / endpoint). A multiplayer suite that spins up\n * 5+ users per spec would hit the limit and start failing in non-obvious\n * ways. This module signs each account in *once*, persists the\n * resulting browser cookies + storage to disk, and reuses the file on\n * subsequent runs.\n *\n * Cache layout:\n * ~/.deepspace/playwright-states/<sha256(email)>.json\n *\n * Validity: a cached file is reused if Playwright successfully loads it\n * AND the resulting context produces a non-anonymous session on the\n * target app. If validation fails, we fall back to a fresh sign-in and\n * overwrite the cache.\n */\n\nimport { createHash } from 'node:crypto'\nimport { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport type { Browser, BrowserContext } from '@playwright/test'\n\nconst STATES_DIR = join(homedir(), '.deepspace', 'playwright-states')\n\n/** Default freshness window — re-sign-in if the cache is older than this. */\nconst DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000 // 7 days\n\nfunction statePathForEmail(email: string): string {\n const hash = createHash('sha256').update(email.trim().toLowerCase()).digest('hex').slice(0, 24)\n return join(STATES_DIR, `${hash}.json`)\n}\n\nfunction ensureStatesDir() {\n if (!existsSync(STATES_DIR)) mkdirSync(STATES_DIR, { recursive: true })\n}\n\n/** Returns true if a cache file exists and is younger than `maxAgeMs`. */\nfunction isCacheFresh(path: string, maxAgeMs: number): boolean {\n if (!existsSync(path)) return false\n try {\n const age = Date.now() - statSync(path).mtimeMs\n return age < maxAgeMs\n } catch {\n return false\n }\n}\n\n/**\n * Sign `account` in by POSTing `/api/auth/sign-in/email` against\n * `baseURL` and capturing the resulting cookies. Returns the path to the\n * Playwright `storageState` JSON file.\n *\n * The browser-driven approach is intentional: we want the cookies set\n * on the same origin Playwright contexts will load from, otherwise the\n * Set-Cookie domain attribute won't match.\n */\nasync function signInAndSaveState(\n browser: Browser,\n account: { email: string; password: string },\n baseURL: string,\n outPath: string,\n): Promise<string> {\n ensureStatesDir()\n const ctx = await browser.newContext({ baseURL })\n try {\n const page = await ctx.newPage()\n // Hit the app first so the origin is established (some auth plugins\n // require a same-origin Origin header on sign-in).\n await page.goto('/')\n const ok = await page.evaluate(\n async ({ email, password }: { email: string; password: string }) => {\n const res = await fetch('/api/auth/sign-in/email', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email, password }),\n })\n return res.ok\n },\n account,\n )\n if (!ok) {\n throw new Error(\n `Sign-in failed for ${account.email}. Check the password is current; ` +\n `if the account was deleted on the server, re-create with ` +\n `\\`deepspace test accounts create\\`.`,\n )\n }\n await ctx.storageState({ path: outPath })\n return outPath\n } finally {\n await ctx.close()\n }\n}\n\nexport interface EnsureStorageStateOptions {\n /** Max age of a cached state file before we re-sign-in. Default 7 days. */\n maxAgeMs?: number\n /** Force a fresh sign-in even if the cache is fresh. */\n force?: boolean\n}\n\n/**\n * Ensure a Playwright `storageState` file exists for `account` and\n * return its path. Signs in once if the cache is missing, stale, or\n * `force: true`.\n */\nexport async function ensureStorageState(\n browser: Browser,\n account: { email: string; password: string },\n baseURL: string,\n options: EnsureStorageStateOptions = {},\n): Promise<string> {\n const path = statePathForEmail(account.email)\n const maxAgeMs = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS\n\n if (!options.force && isCacheFresh(path, maxAgeMs)) {\n return path\n }\n\n return signInAndSaveState(browser, account, baseURL, path)\n}\n\n/**\n * Convenience: open a fresh `BrowserContext` for `account` using the\n * cached storage state (signing in if needed). The caller is responsible\n * for closing the context.\n */\nexport async function newSignedInContext(\n browser: Browser,\n account: { email: string; password: string },\n baseURL: string,\n options: EnsureStorageStateOptions = {},\n): Promise<BrowserContext> {\n const statePath = await ensureStorageState(browser, account, baseURL, options)\n return browser.newContext({ storageState: statePath, baseURL })\n}\n\n/** Read a cached storageState file (mostly for debugging). */\nexport function readCachedState(email: string): unknown | null {\n const path = statePathForEmail(email)\n if (!existsSync(path)) return null\n try {\n return JSON.parse(readFileSync(path, 'utf-8'))\n } catch {\n return null\n }\n}\n\n/** Where the cache for `email` lives. Useful for diagnostics. */\nexport function getStatePathForEmail(email: string): string {\n return statePathForEmail(email)\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readFileSync","homedir","join"]}
package/dist/worker.d.ts CHANGED
@@ -1267,9 +1267,6 @@ interface HandlerContext {
1267
1267
  sendBinary(ws: WebSocket, data: Uint8Array): void;
1268
1268
  }
1269
1269
 
1270
- /** Standard system-managed user columns shared by browser and Worker schemas. */
1271
- declare const USERS_COLUMNS: readonly ColumnDefinition[];
1272
-
1273
1270
  /**
1274
1271
  * Collection Schema Definitions & Validation
1275
1272
  *
@@ -1345,6 +1342,8 @@ declare function canDelete(schema: CollectionSchema, role: string, record: {
1345
1342
  declare function checkFieldPermissions(schema: CollectionSchema, role: string, newData: Record<string, unknown>, existingData?: Record<string, unknown>): string | null;
1346
1343
  /** Names of columns managed by the system (registerUser), not client mutations. */
1347
1344
  declare const SYSTEM_MANAGED_COLUMNS: Set<string>;
1345
+ /** Standard user columns. Apps spread these into their users schema. */
1346
+ declare const USERS_COLUMNS: ColumnDefinition[];
1348
1347
  declare const BASE_USERS_SCHEMA: CollectionSchema;
1349
1348
  /**
1350
1349
  * Lint a CollectionSchema for declarations that look like they should
@@ -2088,6 +2087,14 @@ type CustomBinding = {
2088
2087
  type: 'hyperdrive';
2089
2088
  name: string;
2090
2089
  id: string;
2090
+ } | {
2091
+ type: 'ratelimit';
2092
+ name: string;
2093
+ namespace_id: string;
2094
+ simple: {
2095
+ limit: number;
2096
+ period: 10 | 60;
2097
+ };
2091
2098
  };
2092
2099
  type CustomBindingManifest = CustomBinding[];
2093
2100
  /** Sentinel string in an ID field that requests platform-side provisioning. */
@@ -2593,8 +2600,8 @@ interface DeepSpaceAgentProfile {
2593
2600
  * deployed app underneath a commit.
2594
2601
  */
2595
2602
  declare const DEEPSPACE_MODEL_CATALOG_PROVENANCE: {
2596
- readonly version: "2026-08-03";
2597
- readonly verifiedAt: "2026-08-03";
2603
+ readonly version: "2026-08-04";
2604
+ readonly verifiedAt: "2026-08-04";
2598
2605
  readonly sources: {
2599
2606
  readonly anthropic: "https://platform.claude.com/docs/en/api/beta/models/list";
2600
2607
  readonly openai: "https://developers.openai.com/api/docs/models/all";
@@ -2706,7 +2713,8 @@ declare const DEEPSPACE_AGENT_PROFILES: {
2706
2713
  readonly application: {
2707
2714
  readonly id: "application";
2708
2715
  readonly defaultModel: "claude-sonnet-5";
2709
- readonly maxSteps: 5;
2716
+ readonly maxSteps: 21;
2717
+ readonly maxToolCalls: 20;
2710
2718
  readonly allowedTools: "application-defined";
2711
2719
  };
2712
2720
  readonly documentation: {
@@ -2714,7 +2722,7 @@ declare const DEEPSPACE_AGENT_PROFILES: {
2714
2722
  readonly defaultModel: "claude-sonnet-5";
2715
2723
  readonly maxSteps: 21;
2716
2724
  readonly maxToolCalls: 20;
2717
- readonly allowedTools: readonly ["docs_search", "docs_read"];
2725
+ readonly allowedTools: readonly ["documentation_search", "documentation_read"];
2718
2726
  };
2719
2727
  };
2720
2728
  type DeepSpaceAIModelId = (typeof DEEPSPACE_AI_MODELS)[number]['id'];
@@ -2728,9 +2736,6 @@ interface ResolvedDeepSpaceAgentModel {
2728
2736
  }
2729
2737
  declare function resolveDeepSpaceAgentModel(modelId: unknown, profileId?: DeepSpaceAgentProfileId): ResolvedDeepSpaceAgentModel | null;
2730
2738
 
2731
- /** Distinguish generated docs resources from authored page routes, including dotted slugs. */
2732
- declare function isDocsResourcePath(pathname: string): boolean;
2733
-
2734
2739
  /**
2735
2740
  * Wildcard-aware CORS origin matching for the platform workers.
2736
2741
  *
@@ -3222,10 +3227,9 @@ declare function buildCronContext(env: CronEnv, ownerUserId: string, roomId?: st
3222
3227
  * Usage:
3223
3228
  *
3224
3229
  * // Server-side autonomous — no auth config needed
3225
- * import { createDeepSpaceAI, DEEPSPACE_AI_DEFAULTS, getDeepSpaceAIModel } from 'deepspace/worker'
3226
- * const selected = getDeepSpaceAIModel(DEEPSPACE_AI_DEFAULTS.directGeneration)!
3227
- * const provider = createDeepSpaceAI(env, selected.provider)
3228
- * const result = await generateText({ model: provider(selected.id), ... })
3230
+ * import { createDeepSpaceAI } from 'deepspace/worker'
3231
+ * const cerebras = createDeepSpaceAI(env, 'cerebras')
3232
+ * const result = await generateText({ model: cerebras('llama-3.3-70b'), ... })
3229
3233
  *
3230
3234
  * // User-initiated (inside a request handler)
3231
3235
  * const jwt = c.req.header('Authorization')!.slice(7)
@@ -3289,11 +3293,7 @@ declare class DeepSpaceAgentProfileError extends Error {
3289
3293
  readonly code = "agent_profile_violation";
3290
3294
  constructor(profile: DeepSpaceAgentProfileId, toolName: string);
3291
3295
  }
3292
- /**
3293
- * Run every DeepSpace text agent through one provider, model, and tool-loop
3294
- * implementation. Agent surfaces supply only their profile-specific prompt,
3295
- * messages, tools, persistence callbacks, and access token.
3296
- */
3296
+ /** The sole provider/model/tool-loop implementation for every DeepSpace agent surface. */
3297
3297
  declare function streamDeepSpaceAgent<TOOLS extends ToolSet>(env: DeepSpaceAIEnv, options: DeepSpaceAgentStreamOptions<TOOLS>): DeepSpaceAgentStream<TOOLS>;
3298
3298
  interface DeepSpaceAgentDiagnosticContext {
3299
3299
  profile?: DeepSpaceAgentProfileId;
@@ -3318,12 +3318,11 @@ declare function deepSpaceAgentErrorSummary(error: unknown, context?: DeepSpaceA
3318
3318
  *
3319
3319
  * Usage (inside a request handler, e.g. /api/ai/chat):
3320
3320
  *
3321
- * import { composioTools, streamDeepSpaceAgent } from 'deepspace/worker'
3321
+ * import { composioTools, createDeepSpaceAI } from 'deepspace/worker'
3322
3322
  * const jwt = c.req.header('Authorization')!.slice(7)
3323
3323
  * const tools = await composioTools(c.env, { toolkit: 'gmail', authToken: jwt })
3324
- * const { result } = streamDeepSpaceAgent(c.env, {
3325
- * profile: 'application', authToken: jwt, prompt, tools,
3326
- * })
3324
+ * const ai = createDeepSpaceAI(c.env, 'anthropic', { authToken: jwt })
3325
+ * const result = streamText({ model: ai('claude-sonnet-5'), prompt, tools })
3327
3326
  *
3328
3327
  * Merge with your own tools: `tools: { ...buildTools(executor), ...composio }`.
3329
3328
  */
@@ -3565,155 +3564,6 @@ declare function appendMessage(stub: DurableObjectStub, msg: {
3565
3564
  parts?: unknown[];
3566
3565
  }): Promise<void>;
3567
3566
 
3568
- interface DocsCorpusChunk {
3569
- id: string;
3570
- route: string;
3571
- title: string;
3572
- heading?: string;
3573
- text: string;
3574
- }
3575
- interface DocsSearchResult {
3576
- route: string;
3577
- title: string;
3578
- heading?: string;
3579
- excerpt: string;
3580
- score: number;
3581
- }
3582
- /** Validate the generated assistant index before any content reaches a model. */
3583
- declare function parseDocsCorpus(value: unknown): DocsCorpusChunk[];
3584
- /** Deterministic local lexical retrieval used by the docs agent tools. */
3585
- declare function searchDocsCorpus(corpus: DocsCorpusChunk[], query: string, limit?: number): DocsSearchResult[];
3586
- declare function readDocsRoute(corpus: DocsCorpusChunk[], route: string): Array<Pick<DocsCorpusChunk, 'route' | 'title' | 'heading' | 'text'>>;
3587
- declare function buildDocsAssistantTools(corpus: DocsCorpusChunk[]): ToolSet;
3588
- declare function buildDocsAssistantPrompt(appName: string, currentRoute: string | undefined): string;
3589
- declare function normalizeDocsRoute(value: string): string;
3590
-
3591
- /**
3592
- * Durable, per-app abuse boundary for the public documentation assistant.
3593
- * Bind one SQLite Durable Object instance per immutable app id.
3594
- */
3595
- declare class DocsAssistantLimiter {
3596
- private readonly sql;
3597
- constructor(state: DurableObjectState);
3598
- fetch(request: Request): Promise<Response>;
3599
- private acquire;
3600
- private release;
3601
- private consumeWindow;
3602
- }
3603
-
3604
- interface DocsAssistantRouteEnv extends DeepSpaceAIEnv {
3605
- ASSETS: Fetcher;
3606
- APP_NAME: string;
3607
- DEEPSPACE_APP_ID: string;
3608
- DOCS_ASSISTANT_LIMITER: DurableObjectNamespace;
3609
- }
3610
- /** Register the standard, read-only documentation assistant endpoint. */
3611
- declare function registerDocsAssistantRoutes<Env extends DocsAssistantRouteEnv>(app: Hono<{
3612
- Bindings: Env;
3613
- }>, resolveAuth: (request: Request, env: Env) => Promise<unknown | null>): void;
3614
- /** Preserve useful provider diagnostics in Workers-for-Platforms string logs. */
3615
- declare const docsAssistantErrorSummary: typeof deepSpaceAgentErrorSummary;
3616
- interface DocsAssistantHistoryMessage {
3617
- role: 'user' | 'assistant';
3618
- content: string;
3619
- }
3620
- /**
3621
- * Accept only the completed, alternating UI turns that precede the current
3622
- * question. The browser remains stateless; the shared agent runner receives a
3623
- * bounded ephemeral transcript on each request.
3624
- */
3625
- declare function normalizeDocsAssistantHistory(value: unknown): DocsAssistantHistoryMessage[];
3626
- /**
3627
- * Own the reserved docs namespace before an existing app's SPA wildcard.
3628
- * The dispatcher rewrites public docs requests into this namespace.
3629
- */
3630
- declare function registerDocsStaticRoutes<Env extends Pick<DocsAssistantRouteEnv, 'ASSETS'>>(app: Hono<{
3631
- Bindings: Env;
3632
- }>): void;
3633
-
3634
- interface DocsMcpRouteEnv {
3635
- ASSETS: Fetcher;
3636
- DEEPSPACE_APP_ID: string;
3637
- DOCS_ASSISTANT_LIMITER: DurableObjectNamespace;
3638
- }
3639
- /** Register the public, stateless MCP endpoint for a documentation surface. */
3640
- declare function registerDocsMcpRoutes<Env extends DocsMcpRouteEnv>(app: Hono<{
3641
- Bindings: Env;
3642
- }>): void;
3643
-
3644
- interface DeepSpaceDocsRouteEnv extends DocsAssistantRouteEnv, DocsMcpRouteEnv {
3645
- }
3646
- interface DeepSpaceDocsRouteOptions<Env extends DeepSpaceDocsRouteEnv> {
3647
- resolveAuth: (request: Request, env: Env) => Promise<unknown | null>;
3648
- }
3649
- /** Register the complete native docs protocol surface in its required order. */
3650
- declare function registerDeepSpaceDocs<Env extends DeepSpaceDocsRouteEnv>(app: Hono<{
3651
- Bindings: Env;
3652
- }>, options: DeepSpaceDocsRouteOptions<Env>): void;
3653
- /** Canonical manifest entry for the public docs assistant/MCP concurrency limiter. */
3654
- declare function docsAssistantLimiterManifestEntry<ClassName extends string>(className: ClassName): {
3655
- readonly binding: "DOCS_ASSISTANT_LIMITER";
3656
- readonly className: ClassName;
3657
- readonly sqlite: true;
3658
- };
3659
-
3660
- interface DocsPublishedManifest {
3661
- sourceHash: string;
3662
- name: string;
3663
- assistant: {
3664
- access: 'disabled' | 'public' | 'authenticated';
3665
- model?: string;
3666
- };
3667
- mcp: {
3668
- access: 'disabled' | 'public';
3669
- };
3670
- }
3671
- interface DocsPublishedEnv {
3672
- ASSETS: Fetcher;
3673
- }
3674
- interface DocsPublishedPage {
3675
- route: string;
3676
- title: string;
3677
- markdown: string;
3678
- }
3679
- type DocsPublishedCorpusResult = {
3680
- ok: true;
3681
- chunks: DocsCorpusChunk[];
3682
- } | {
3683
- ok: false;
3684
- error: unknown;
3685
- };
3686
- /** Read the small, generated policy manifest shared by docs runtime features. */
3687
- declare function loadDocsPublishedManifest(env: DocsPublishedEnv, requestUrl?: string): Promise<DocsPublishedManifest | null>;
3688
- /** Load and validate the generated corpus once per docs source hash. */
3689
- declare function loadDocsPublishedCorpus(env: DocsPublishedEnv, sourceHash: string, requestUrl?: string): Promise<DocsCorpusChunk[]>;
3690
- /** Convert asset and validation failures into one explicit route-level result. */
3691
- declare function resolveDocsPublishedCorpus(env: DocsPublishedEnv, sourceHash: string, requestUrl?: string): Promise<DocsPublishedCorpusResult>;
3692
- /** Read the complete generated Markdown only for a route in the public corpus. */
3693
- declare function readDocsPublishedPage(env: DocsPublishedEnv, corpus: DocsCorpusChunk[], requestedRoute: string, requestUrl?: string): Promise<DocsPublishedPage | null>;
3694
-
3695
- interface DocsPublicLimiterEnv {
3696
- DEEPSPACE_APP_ID: string;
3697
- DOCS_ASSISTANT_LIMITER: DurableObjectNamespace;
3698
- }
3699
- interface DocsPublicLease {
3700
- leaseId: string;
3701
- stub: DurableObjectStub;
3702
- }
3703
- type DocsPublicLeaseResult = {
3704
- ok: true;
3705
- lease: DocsPublicLease;
3706
- } | {
3707
- ok: false;
3708
- status: 429 | 503;
3709
- };
3710
- /** Hash the edge client address before it reaches durable limiter storage. */
3711
- declare function docsPublicClientKey(value: string): Promise<string>;
3712
- /** Acquire one bounded public docs operation from the per-app limiter. */
3713
- declare function acquireDocsPublicLease(env: DocsPublicLimiterEnv, clientKey: string, logPrefix: string): Promise<DocsPublicLeaseResult>;
3714
- /** Release a concurrency lease; window usage remains consumed. */
3715
- declare function releaseDocsPublicLease(lease: DocsPublicLease | undefined, logPrefix: string): Promise<void>;
3716
-
3717
3567
  /**
3718
3568
  * Per-binding usage metering — record Vectorize / Workers AI / etc. costs
3719
3569
  * to the auto-attached `USAGE_EVENTS` Analytics Engine dataset.
@@ -3919,6 +3769,35 @@ interface RunMigrationsResult {
3919
3769
  */
3920
3770
  declare function runMigrations(db: D1Database, migrations: readonly string[]): Promise<RunMigrationsResult>;
3921
3771
 
3772
+ interface DocumentationAssistantRouteEnv extends DeepSpaceAIEnv {
3773
+ ASSETS: Fetcher;
3774
+ APP_NAME: string;
3775
+ DEEPSPACE_APP_ID: string;
3776
+ DOCUMENTATION_CLIENT_RATE_LIMITER?: RateLimit;
3777
+ DOCUMENTATION_APP_RATE_LIMITER?: RateLimit;
3778
+ }
3779
+
3780
+ interface DocumentationMcpRouteEnv {
3781
+ ASSETS: Fetcher;
3782
+ DEEPSPACE_APP_ID: string;
3783
+ DOCUMENTATION_CLIENT_RATE_LIMITER?: RateLimit;
3784
+ DOCUMENTATION_APP_RATE_LIMITER?: RateLimit;
3785
+ }
3786
+
3787
+ interface DeepSpaceDocumentationRouteEnv extends DocumentationAssistantRouteEnv, DocumentationMcpRouteEnv {
3788
+ }
3789
+ interface DeepSpaceDocumentationRouteOptions<Env extends DeepSpaceDocumentationRouteEnv> {
3790
+ resolveAuth: (request: Request, env: Env) => Promise<unknown | null>;
3791
+ config?: {
3792
+ domains?: readonly string[];
3793
+ [key: string]: unknown;
3794
+ };
3795
+ }
3796
+ /** Register the complete native documentation protocol surface in its required order. */
3797
+ declare function registerDeepSpaceDocumentation<Env extends DeepSpaceDocumentationRouteEnv>(app: Hono<{
3798
+ Bindings: Env;
3799
+ }>, options: DeepSpaceDocumentationRouteOptions<Env>): void;
3800
+
3922
3801
  /**
3923
3802
  * Auth types for the DeepSpace SDK.
3924
3803
  *
@@ -5259,4 +5138,4 @@ interface AppLogsResponse {
5259
5138
  declare const APP_LOG_EVENT_KEYS: readonly ["id", "timestamp", "level", "eventType", "message", "source", "outcome", "request", "exception"];
5260
5139
  type AppLogEventKey = (typeof APP_LOG_EVENT_KEYS)[number];
5261
5140
 
5262
- export { AI_CHATS_SCHEMA, AI_MESSAGES_SCHEMA, ALLOWED_BINDING_TYPES, APP_ID_RE, APP_LOG_EVENT_KEYS, APP_NAME_RULES, AUTO_PROVISIONABLE_TYPES, AUTO_PROVISION_SENTINEL, type ActionContext, type ActionHandler, type ActionResult, type ActionTools, type ApiWorkerEnv, type AppLogEvent, type AppLogEventKey, type AppLogsResponse, type AppMigrationIdsValidation, type AppNameResolution, type AppNameValidation, type AuthWorkerEnv, Awareness, type AwarenessState, type AwarenessStates, BASE_USERS_SCHEMA, BUILT_IN_TOOLS, type BaseMessage, BaseRoom, CANONICAL_APP_IDENTITY_MIGRATION_ID, CHANNELS_SCHEMA, CHANNEL_INVITATIONS_SCHEMA, CHANNEL_MEMBERS_SCHEMA, CLIENT_LOG_MARKER, CONVERSATION_SCHEMAS, COST_RATES, CanvasRoom, type CanvasShape, type ChatContextConfig, type ChatMessageRow, type ChatRow, type ChatTurn, type ClientErrorKind, type ClientErrorReport, type ClientMessage, type CollectionPermissionSummary, type CollectionSchema, type ColumnDefinition, type ColumnInterpretation, type ComposioToolsOptions, type ConnectionAttachment, type ConvMemberData, type ConvMessageData, type ConvReactionData, type ConvReadCursorData, type ConvVoteData, type ConversationStateData, type CronContext, type CronExecution, CronRoom, type CronRoomConfig, type CronTask, type CustomBinding, type CustomBindingManifest, DEEPSPACE_AGENT_PROFILES, DEEPSPACE_AI_DEFAULTS, DEEPSPACE_AI_MODELS, DEEPSPACE_MODEL_CATALOG_PROVENANCE, DEFAULT_CONTEXT_CONFIG, DEFAULT_DO_MANIFEST, DEFAULT_QUERY_LIMIT, DEPLOYED_APPS_DEFAULTS, DIRECTORY_SCHEMAS, type DOBindings, type DOManifest, type DOManifestEntry, type DeepSpaceAIEnv, type DeepSpaceAIModel, type DeepSpaceAIModelId, type DeepSpaceAIOptions, type DeepSpaceAIProvider, type DeepSpaceAgentDiagnosticContext, DeepSpaceAgentModelError, type DeepSpaceAgentProfile, DeepSpaceAgentProfileError, type DeepSpaceAgentProfileId, type DeepSpaceAgentStream, type DeepSpaceAgentStreamOptions, type DeepSpaceAgentSupport, type DeepSpaceAuth, type DeepSpaceAuthConfig, type DeepSpaceDocsRouteEnv, type DeepSpaceDocsRouteOptions, type DirectoryCommunityData, type DirectoryConversationData, type DirectoryMembershipData, type DirectoryPostData, type DoMigrationDirective, type DoMigrationPlan, type DocsAssistantHistoryMessage, DocsAssistantLimiter, type DocsAssistantRouteEnv, type DocsCorpusChunk, type DocsMcpRouteEnv, type DocsPublicLease, type DocsPublicLeaseResult, type DocsPublicLimiterEnv, type DocsPublishedCorpusResult, type DocsPublishedEnv, type DocsPublishedManifest, type DocsPublishedPage, type DocsSearchResult, type ExistingDOBinding, GLOBAL_DO_TYPES, GLOBAL_DO_TYPE_NAMES, type GameInput, GameRoom, type GameRoomConfig, type GetActionData, type GlobalDOType, type HandlerContext, type Job, type JobContext, JobRoom, type JobRoomConfig, type JobStatus, type JwtClaims, type JwtVerifierConfig, LOG_LEVELS, type LogLevel, MESSAGES_SCHEMA, MSG, MSG_AWARENESS, MSG_SYNC, MSG_SYNC_STEP1, MSG_SYNC_STEP2, MSG_SYNC_UPDATE, MSG_YJS_AWARENESS, MSG_YJS_SYNC, type MessageHandlers, type MsgType, type MutateActionData, type PermissionAnalysis, type PermissionContext, type PermissionLevel, type PermissionSource, type PlatformWorkerEnv, type Player, type PrefixResult, type PresencePeer, PresenceRoom, type ProtocolMessage, type QueryActionData, REACTIONS_SCHEMA, READ_RECEIPTS_SCHEMA, REGISTRY_INSTANCE, RESERVED_BINDING_NAMES, RESERVED_COLLECTION_NAMES, RESOURCE_ID_RE, ROLE_ADMIN, ROLE_ANONYMOUS, ROLE_DEFAULT, RecordRoom, type RecordRoomConfig, RegistryClientError, type ResolvedColumn, type ResolvedDeepSpaceAgentModel, type ResolvedPermission, type RolePermissions, type RunMigrationsResult, STRICT_APP_ID_RE, SYSTEM_COLLECTIONS, SYSTEM_MANAGED_COLUMNS, SchemaRegistry, type ScopeContext, type ScopedR2Auth, type ScopedR2Config, type ScopedR2Handler, type ScreenshotEnv, type ScreenshotResult, type ServerMessage, type SharedConnection, type StoredRecord, type Summarizer, type SyncResult, TEST_ACCOUNT_EMAIL_SUFFIX, type TokenDebugInfo, type ToolResult, type ToolSchema, USERS_COLUMNS, type User, type UserAttachment, VOTING_SCHEMAS, type ValidationError, type VerifiedAuth, type VerifyOutcome, type VerifyResult, type Viewport, WORKSPACE_SCHEMAS, YjsRoom, acquireDocsPublicLease, analyzePermissions, apiWorkerFetch, appendMessage, applyAiToolDefaults, applySlidingWindow, authWorkerFetch, bindingManifestFromOutputConfig, buildCronContext, buildDocsAssistantPrompt, buildDocsAssistantTools, buildTableSelect, buildUiParts, callRegistry, canCreate, canDelete, canRead, canUpdate, capToolResultSize, captureScreenshot, checkFieldPermissions, clientBuild, coerceValue, collectionTableName, columnId, composioTools, computeDoMigration, computeHmacHex, createChat, createDecoder, createDeepSpaceAI, createDeepSpaceAuth, createEncoder, createScopedR2Handler, dataToColumnValues, decodeJwtPayload, deepSpaceAgentErrorSummary, deleteChatCascade, dispatch, docsAssistantErrorSummary, docsAssistantLimiterManifestEntry, docsPublicClientKey, encode, encodeAwarenessMessage, encodeSyncStep1, encodeSyncStep2, encodeUpdate, enqueueJob, getChat, getDeepSpaceAIModel, getGlobalDOSchemas, getGlobalDOType, getMessageType, getRolePermissions, handleAwarenessMessage, handleClientErrorReport, handleSyncMessage, hasContent, isAutoProvision, isDocsResourcePath, isOwner, isTestAccountClaims, isTestAccountEmail, isTestAccountTier, lintSchema, listDeepSpaceAgentModels, loadDocsPublishedCorpus, loadDocsPublishedManifest, loadMessages, makeDefaultSummarizer, matchOrigin, meterAi, meterUsage, meterVectorize, noopPermissionContext, normalizeDocsAssistantHistory, normalizeDocsRoute, parseDocsCorpus, parseQuotaLimit, platformCorsPatterns, platformWorkerFetch, prepareMessagesWithCompaction, priceBindingUsageEvent, readDocsPublishedPage, readDocsRoute, readVarUint, readVarUint8Array, registerClientErrorRoute, registerDeepSpaceDocs, registerDocsAssistantRoutes, registerDocsMcpRoutes, registerDocsStaticRoutes, registryErrorJson, registryStub, releaseDocsPublicLease, resolveAppName, resolveColumn, resolveDeepSpaceAgentModel, resolveDocsPublishedCorpus, rowToData, runMigrations, searchDocsCorpus, serverBuild, streamDeepSpaceAgent, timingSafeEqualHex, timingSafeEqualStrings, toUint8Array, totalChars, truncateOldToolResults, turnsToCoreMessages, unwrapToolOutput, updateChat, validateAppMigrationIds, validateAppName, validateBindingManifest, validateDoManifest, verifyJwt, workspaceAccountsSchema, workspaceContentSharesSchema, workspaceEmailHandlesSchema, workspaceFormResponsesSchema, workspacePeopleSchema, workspaceProjectsSchema, workspaceTagsSchema, workspaceTasksSchema, workspaceTeamMembersSchema, workspaceTeamsSchema, workspaceTransactionsSchema, writeVarUint, writeVarUint8Array };
5141
+ export { AI_CHATS_SCHEMA, AI_MESSAGES_SCHEMA, ALLOWED_BINDING_TYPES, APP_ID_RE, APP_LOG_EVENT_KEYS, APP_NAME_RULES, AUTO_PROVISIONABLE_TYPES, AUTO_PROVISION_SENTINEL, type ActionContext, type ActionHandler, type ActionResult, type ActionTools, type ApiWorkerEnv, type AppLogEvent, type AppLogEventKey, type AppLogsResponse, type AppMigrationIdsValidation, type AppNameResolution, type AppNameValidation, type AuthWorkerEnv, Awareness, type AwarenessState, type AwarenessStates, BASE_USERS_SCHEMA, BUILT_IN_TOOLS, type BaseMessage, BaseRoom, CANONICAL_APP_IDENTITY_MIGRATION_ID, CHANNELS_SCHEMA, CHANNEL_INVITATIONS_SCHEMA, CHANNEL_MEMBERS_SCHEMA, CLIENT_LOG_MARKER, CONVERSATION_SCHEMAS, COST_RATES, CanvasRoom, type CanvasShape, type ChatContextConfig, type ChatMessageRow, type ChatRow, type ChatTurn, type ClientErrorKind, type ClientErrorReport, type ClientMessage, type CollectionPermissionSummary, type CollectionSchema, type ColumnDefinition, type ColumnInterpretation, type ComposioToolsOptions, type ConnectionAttachment, type ConvMemberData, type ConvMessageData, type ConvReactionData, type ConvReadCursorData, type ConvVoteData, type ConversationStateData, type CronContext, type CronExecution, CronRoom, type CronRoomConfig, type CronTask, type CustomBinding, type CustomBindingManifest, DEEPSPACE_AGENT_PROFILES, DEEPSPACE_AI_DEFAULTS, DEEPSPACE_AI_MODELS, DEEPSPACE_MODEL_CATALOG_PROVENANCE, DEFAULT_CONTEXT_CONFIG, DEFAULT_DO_MANIFEST, DEFAULT_QUERY_LIMIT, DEPLOYED_APPS_DEFAULTS, DIRECTORY_SCHEMAS, type DOBindings, type DOManifest, type DOManifestEntry, type DeepSpaceAIEnv, type DeepSpaceAIModel, type DeepSpaceAIModelId, type DeepSpaceAIOptions, type DeepSpaceAIProvider, type DeepSpaceAgentDiagnosticContext, DeepSpaceAgentModelError, type DeepSpaceAgentProfile, DeepSpaceAgentProfileError, type DeepSpaceAgentProfileId, type DeepSpaceAgentStream, type DeepSpaceAgentStreamOptions, type DeepSpaceAgentSupport, type DeepSpaceAuth, type DeepSpaceAuthConfig, type DeepSpaceDocumentationRouteEnv, type DeepSpaceDocumentationRouteOptions, type DirectoryCommunityData, type DirectoryConversationData, type DirectoryMembershipData, type DirectoryPostData, type DoMigrationDirective, type DoMigrationPlan, type ExistingDOBinding, GLOBAL_DO_TYPES, GLOBAL_DO_TYPE_NAMES, type GameInput, GameRoom, type GameRoomConfig, type GetActionData, type GlobalDOType, type HandlerContext, type Job, type JobContext, JobRoom, type JobRoomConfig, type JobStatus, type JwtClaims, type JwtVerifierConfig, LOG_LEVELS, type LogLevel, MESSAGES_SCHEMA, MSG, MSG_AWARENESS, MSG_SYNC, MSG_SYNC_STEP1, MSG_SYNC_STEP2, MSG_SYNC_UPDATE, MSG_YJS_AWARENESS, MSG_YJS_SYNC, type MessageHandlers, type MsgType, type MutateActionData, type PermissionAnalysis, type PermissionContext, type PermissionLevel, type PermissionSource, type PlatformWorkerEnv, type Player, type PrefixResult, type PresencePeer, PresenceRoom, type ProtocolMessage, type QueryActionData, REACTIONS_SCHEMA, READ_RECEIPTS_SCHEMA, REGISTRY_INSTANCE, RESERVED_BINDING_NAMES, RESERVED_COLLECTION_NAMES, RESOURCE_ID_RE, ROLE_ADMIN, ROLE_ANONYMOUS, ROLE_DEFAULT, RecordRoom, type RecordRoomConfig, RegistryClientError, type ResolvedColumn, type ResolvedDeepSpaceAgentModel, type ResolvedPermission, type RolePermissions, type RunMigrationsResult, STRICT_APP_ID_RE, SYSTEM_COLLECTIONS, SYSTEM_MANAGED_COLUMNS, SchemaRegistry, type ScopeContext, type ScopedR2Auth, type ScopedR2Config, type ScopedR2Handler, type ScreenshotEnv, type ScreenshotResult, type ServerMessage, type SharedConnection, type StoredRecord, type Summarizer, type SyncResult, TEST_ACCOUNT_EMAIL_SUFFIX, type TokenDebugInfo, type ToolResult, type ToolSchema, USERS_COLUMNS, type User, type UserAttachment, VOTING_SCHEMAS, type ValidationError, type VerifiedAuth, type VerifyOutcome, type VerifyResult, type Viewport, WORKSPACE_SCHEMAS, YjsRoom, analyzePermissions, apiWorkerFetch, appendMessage, applyAiToolDefaults, applySlidingWindow, authWorkerFetch, bindingManifestFromOutputConfig, buildCronContext, buildTableSelect, buildUiParts, callRegistry, canCreate, canDelete, canRead, canUpdate, capToolResultSize, captureScreenshot, checkFieldPermissions, clientBuild, coerceValue, collectionTableName, columnId, composioTools, computeDoMigration, computeHmacHex, createChat, createDecoder, createDeepSpaceAI, createDeepSpaceAuth, createEncoder, createScopedR2Handler, dataToColumnValues, decodeJwtPayload, deepSpaceAgentErrorSummary, deleteChatCascade, dispatch, encode, encodeAwarenessMessage, encodeSyncStep1, encodeSyncStep2, encodeUpdate, enqueueJob, getChat, getDeepSpaceAIModel, getGlobalDOSchemas, getGlobalDOType, getMessageType, getRolePermissions, handleAwarenessMessage, handleClientErrorReport, handleSyncMessage, hasContent, isAutoProvision, isOwner, isTestAccountClaims, isTestAccountEmail, isTestAccountTier, lintSchema, listDeepSpaceAgentModels, loadMessages, makeDefaultSummarizer, matchOrigin, meterAi, meterUsage, meterVectorize, noopPermissionContext, parseQuotaLimit, platformCorsPatterns, platformWorkerFetch, prepareMessagesWithCompaction, priceBindingUsageEvent, readVarUint, readVarUint8Array, registerClientErrorRoute, registerDeepSpaceDocumentation, registryErrorJson, registryStub, resolveAppName, resolveColumn, resolveDeepSpaceAgentModel, rowToData, runMigrations, serverBuild, streamDeepSpaceAgent, timingSafeEqualHex, timingSafeEqualStrings, toUint8Array, totalChars, truncateOldToolResults, turnsToCoreMessages, unwrapToolOutput, updateChat, validateAppMigrationIds, validateAppName, validateBindingManifest, validateDoManifest, verifyJwt, workspaceAccountsSchema, workspaceContentSharesSchema, workspaceEmailHandlesSchema, workspaceFormResponsesSchema, workspacePeopleSchema, workspaceProjectsSchema, workspaceTagsSchema, workspaceTasksSchema, workspaceTeamMembersSchema, workspaceTeamsSchema, workspaceTransactionsSchema, writeVarUint, writeVarUint8Array };