deepspace 0.6.1 → 0.6.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.
package/dist/server.d.ts CHANGED
@@ -615,7 +615,7 @@ declare function createDeepSpaceAI(env: DeepSpaceAIEnv, provider: Provider, opti
615
615
  * const jwt = c.req.header('Authorization')!.slice(7)
616
616
  * const tools = await composioTools(c.env, { toolkit: 'gmail', authToken: jwt })
617
617
  * const ai = createDeepSpaceAI(c.env, 'anthropic', { authToken: jwt })
618
- * const result = streamText({ model: ai('claude-sonnet-4-6'), prompt, tools })
618
+ * const result = streamText({ model: ai('claude-sonnet-5'), prompt, tools })
619
619
  *
620
620
  * Merge with your own tools: `tools: { ...buildTools(executor), ...composio }`.
621
621
  */
@@ -1373,6 +1373,8 @@ type ServerMessage = BaseMessage<typeof MSG.QUERY_RESULT, {
1373
1373
  * - Raw SQLite access via this.sql
1374
1374
  * - Broadcast helpers: broadcast(), sendTo()
1375
1375
  * - HTTP fetch handler with WebSocket upgrade detection
1376
+ * - Internal control-plane endpoint: POST /internal/disconnect-sockets
1377
+ * (force every client to reconnect and resync after out-of-band writes)
1376
1378
  *
1377
1379
  * Subclasses implement lifecycle hooks:
1378
1380
  * onConnect, onMessage, onBinaryMessage, onDisconnect, onRequest, onAlarm
@@ -1392,6 +1394,21 @@ declare abstract class BaseRoom<E = Record<string, unknown>> {
1392
1394
  protected sql: SqlStorage;
1393
1395
  constructor(state: DurableObjectState, env: unknown);
1394
1396
  fetch(request: Request): Promise<Response>;
1397
+ /**
1398
+ * Handle built-in `/internal/*` control-plane routes shared by every room
1399
+ * type. Returns a `Response` if the request was an internal route, or `null`
1400
+ * to let the caller continue normal dispatch.
1401
+ *
1402
+ * Currently:
1403
+ * - `POST /internal/disconnect-sockets` — close every live WebSocket so
1404
+ * clients reconnect and resync. Optional JSON body `{ code?, reason? }`
1405
+ * overrides the defaults (1012 / 'state-refresh'). Responds with
1406
+ * `{ success: true, closed: <n> }`.
1407
+ *
1408
+ * See the security note on `fetch()`: this path is only reachable via DO
1409
+ * stub fetch from the app worker, never from the public internet.
1410
+ */
1411
+ protected handleInternalRequest(request: Request, url: URL): Promise<Response | null>;
1395
1412
  private handleWebSocketUpgrade;
1396
1413
  webSocketMessage(ws: WebSocket, message: ArrayBuffer | string): Promise<void>;
1397
1414
  webSocketClose(ws: WebSocket, _code: number, _reason: string): Promise<void>;
@@ -1430,6 +1447,33 @@ declare abstract class BaseRoom<E = Record<string, unknown>> {
1430
1447
  * Get all connected WebSockets.
1431
1448
  */
1432
1449
  protected getWebSockets(): WebSocket[];
1450
+ /**
1451
+ * Force every connected client to reconnect and resync by closing all live
1452
+ * WebSockets. Returns the number of sockets closed.
1453
+ *
1454
+ * Use this after an **out-of-band, server-side write** to the room's records
1455
+ * — an admin import route, a migration script, a cron job, or a server
1456
+ * action — that connected clients have no way to learn about. Without it, a
1457
+ * browser tab holding stale in-memory state keeps operating on (and may
1458
+ * autosave over) data that changed underneath it. Closing the socket makes
1459
+ * the client SDK reconnect and pull fresh query results.
1460
+ *
1461
+ * The default close code is 1012 ("service restart") with reason
1462
+ * 'state-refresh'. The DeepSpace client treats *any* close as a reconnect
1463
+ * trigger (it does not special-case clean/1000 closes), so on reconnect it
1464
+ * re-subscribes every active query and receives fresh `QUERY_RESULT`s —
1465
+ * `useQuery` consumers see the new data without re-subscribing.
1466
+ *
1467
+ * Each close is guarded so one already-closing socket can't abort the sweep.
1468
+ *
1469
+ * @param options.code WebSocket close code (default 1012).
1470
+ * @param options.reason WebSocket close reason (default 'state-refresh').
1471
+ * @returns the number of sockets that were closed.
1472
+ */
1473
+ disconnectAllSockets(options?: {
1474
+ code?: number;
1475
+ reason?: string;
1476
+ }): number;
1433
1477
  /**
1434
1478
  * Get the user attachment for a WebSocket.
1435
1479
  */
@@ -1751,6 +1795,14 @@ declare class RecordRoom<E = Record<string, unknown>> extends BaseRoom<E> {
1751
1795
  * truthy value on the worker to opt in). Off by default.
1752
1796
  */
1753
1797
  private get perfLogEnabled();
1798
+ /**
1799
+ * The HTTP debug API (`/api/debug/*`) runs arbitrary SQL and role changes
1800
+ * with no auth of its own, so it is gated here at the DO's single ingress.
1801
+ * Off unless a deployment opts in with `ALLOW_DEBUG_ROUTES=true`
1802
+ * (`deepspace dev`/`test` set it automatically). Deployments holding shared
1803
+ * data override this to always return false.
1804
+ */
1805
+ protected get debugRoutesEnabled(): boolean;
1754
1806
  constructor(state: DurableObjectState, env: unknown, schemas?: CollectionSchema[], config?: RecordRoomConfig);
1755
1807
  private getPermissionContext;
1756
1808
  fetch(request: Request): Promise<Response>;
package/dist/server.js CHANGED
@@ -1287,6 +1287,8 @@ var BaseRoom = class {
1287
1287
  // ==========================================================================
1288
1288
  async fetch(request) {
1289
1289
  const url = new URL(request.url);
1290
+ const internal = await this.handleInternalRequest(request, url);
1291
+ if (internal) return internal;
1290
1292
  if (request.headers.get("Upgrade") === "websocket") {
1291
1293
  return this.handleWebSocketUpgrade(request, url);
1292
1294
  }
@@ -1296,6 +1298,36 @@ var BaseRoom = class {
1296
1298
  return new Response("Not Found", { status: 404 });
1297
1299
  }
1298
1300
  // ==========================================================================
1301
+ // Internal Control-Plane Endpoints
1302
+ // ==========================================================================
1303
+ /**
1304
+ * Handle built-in `/internal/*` control-plane routes shared by every room
1305
+ * type. Returns a `Response` if the request was an internal route, or `null`
1306
+ * to let the caller continue normal dispatch.
1307
+ *
1308
+ * Currently:
1309
+ * - `POST /internal/disconnect-sockets` — close every live WebSocket so
1310
+ * clients reconnect and resync. Optional JSON body `{ code?, reason? }`
1311
+ * overrides the defaults (1012 / 'state-refresh'). Responds with
1312
+ * `{ success: true, closed: <n> }`.
1313
+ *
1314
+ * See the security note on `fetch()`: this path is only reachable via DO
1315
+ * stub fetch from the app worker, never from the public internet.
1316
+ */
1317
+ async handleInternalRequest(request, url) {
1318
+ if (request.method === "POST" && url.pathname === "/internal/disconnect-sockets") {
1319
+ let options;
1320
+ try {
1321
+ const text = await request.text();
1322
+ if (text) options = JSON.parse(text);
1323
+ } catch {
1324
+ }
1325
+ const closed = this.disconnectAllSockets(options);
1326
+ return Response.json({ success: true, closed });
1327
+ }
1328
+ return null;
1329
+ }
1330
+ // ==========================================================================
1299
1331
  // WebSocket Connection
1300
1332
  // ==========================================================================
1301
1333
  async handleWebSocketUpgrade(request, url) {
@@ -1382,6 +1414,42 @@ var BaseRoom = class {
1382
1414
  getWebSockets() {
1383
1415
  return this.state.getWebSockets();
1384
1416
  }
1417
+ /**
1418
+ * Force every connected client to reconnect and resync by closing all live
1419
+ * WebSockets. Returns the number of sockets closed.
1420
+ *
1421
+ * Use this after an **out-of-band, server-side write** to the room's records
1422
+ * — an admin import route, a migration script, a cron job, or a server
1423
+ * action — that connected clients have no way to learn about. Without it, a
1424
+ * browser tab holding stale in-memory state keeps operating on (and may
1425
+ * autosave over) data that changed underneath it. Closing the socket makes
1426
+ * the client SDK reconnect and pull fresh query results.
1427
+ *
1428
+ * The default close code is 1012 ("service restart") with reason
1429
+ * 'state-refresh'. The DeepSpace client treats *any* close as a reconnect
1430
+ * trigger (it does not special-case clean/1000 closes), so on reconnect it
1431
+ * re-subscribes every active query and receives fresh `QUERY_RESULT`s —
1432
+ * `useQuery` consumers see the new data without re-subscribing.
1433
+ *
1434
+ * Each close is guarded so one already-closing socket can't abort the sweep.
1435
+ *
1436
+ * @param options.code WebSocket close code (default 1012).
1437
+ * @param options.reason WebSocket close reason (default 'state-refresh').
1438
+ * @returns the number of sockets that were closed.
1439
+ */
1440
+ disconnectAllSockets(options) {
1441
+ const code = options?.code ?? 1012;
1442
+ const reason = options?.reason ?? "state-refresh";
1443
+ let closed = 0;
1444
+ for (const ws of this.state.getWebSockets()) {
1445
+ try {
1446
+ ws.close(code, reason);
1447
+ closed++;
1448
+ } catch {
1449
+ }
1450
+ }
1451
+ return closed;
1452
+ }
1385
1453
  /**
1386
1454
  * Get the user attachment for a WebSocket.
1387
1455
  */
@@ -3481,6 +3549,16 @@ var RecordRoom = class extends BaseRoom {
3481
3549
  get perfLogEnabled() {
3482
3550
  return !!this.env?.DEEPSPACE_DO_PERF;
3483
3551
  }
3552
+ /**
3553
+ * The HTTP debug API (`/api/debug/*`) runs arbitrary SQL and role changes
3554
+ * with no auth of its own, so it is gated here at the DO's single ingress.
3555
+ * Off unless a deployment opts in with `ALLOW_DEBUG_ROUTES=true`
3556
+ * (`deepspace dev`/`test` set it automatically). Deployments holding shared
3557
+ * data override this to always return false.
3558
+ */
3559
+ get debugRoutesEnabled() {
3560
+ return this.env?.ALLOW_DEBUG_ROUTES === "true";
3561
+ }
3484
3562
  constructor(state, env, schemas = [], config = {}) {
3485
3563
  super(state, env);
3486
3564
  this.schemaRegistry = new SchemaRegistry([
@@ -3516,14 +3594,21 @@ var RecordRoom = class extends BaseRoom {
3516
3594
  // HTTP Entry Point (overrides BaseRoom.fetch)
3517
3595
  // ============================================================================
3518
3596
  async fetch(request) {
3597
+ const url = new URL(request.url);
3598
+ if (url.pathname.startsWith("/internal/")) {
3599
+ const internal = await this.handleInternalRequest(request, url);
3600
+ if (internal) return internal;
3601
+ }
3519
3602
  const fetchStart = Date.now();
3520
3603
  const isColdStart = this.freshConstruct;
3521
3604
  const needsInit = !this.initPromise;
3522
3605
  this.freshConstruct = false;
3523
3606
  await this.ensureInitialized();
3524
3607
  const initMs = Date.now() - fetchStart;
3525
- const url = new URL(request.url);
3526
3608
  if (url.pathname.startsWith("/api/")) {
3609
+ if (url.pathname.startsWith("/api/debug/") && !this.debugRoutesEnabled) {
3610
+ return new Response("Not Found", { status: 404 });
3611
+ }
3527
3612
  return handleApiRequest(this.createHandlerContext(), request, url);
3528
3613
  }
3529
3614
  if (request.headers.get("Upgrade") === "websocket") {