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/worker.d.ts CHANGED
@@ -898,6 +898,8 @@ declare function encode<M extends BaseMessage<string, unknown>>(message: M): str
898
898
  * - Raw SQLite access via this.sql
899
899
  * - Broadcast helpers: broadcast(), sendTo()
900
900
  * - HTTP fetch handler with WebSocket upgrade detection
901
+ * - Internal control-plane endpoint: POST /internal/disconnect-sockets
902
+ * (force every client to reconnect and resync after out-of-band writes)
901
903
  *
902
904
  * Subclasses implement lifecycle hooks:
903
905
  * onConnect, onMessage, onBinaryMessage, onDisconnect, onRequest, onAlarm
@@ -917,6 +919,21 @@ declare abstract class BaseRoom<E = Record<string, unknown>> {
917
919
  protected sql: SqlStorage;
918
920
  constructor(state: DurableObjectState, env: unknown);
919
921
  fetch(request: Request): Promise<Response>;
922
+ /**
923
+ * Handle built-in `/internal/*` control-plane routes shared by every room
924
+ * type. Returns a `Response` if the request was an internal route, or `null`
925
+ * to let the caller continue normal dispatch.
926
+ *
927
+ * Currently:
928
+ * - `POST /internal/disconnect-sockets` — close every live WebSocket so
929
+ * clients reconnect and resync. Optional JSON body `{ code?, reason? }`
930
+ * overrides the defaults (1012 / 'state-refresh'). Responds with
931
+ * `{ success: true, closed: <n> }`.
932
+ *
933
+ * See the security note on `fetch()`: this path is only reachable via DO
934
+ * stub fetch from the app worker, never from the public internet.
935
+ */
936
+ protected handleInternalRequest(request: Request, url: URL): Promise<Response | null>;
920
937
  private handleWebSocketUpgrade;
921
938
  webSocketMessage(ws: WebSocket, message: ArrayBuffer | string): Promise<void>;
922
939
  webSocketClose(ws: WebSocket, _code: number, _reason: string): Promise<void>;
@@ -955,6 +972,33 @@ declare abstract class BaseRoom<E = Record<string, unknown>> {
955
972
  * Get all connected WebSockets.
956
973
  */
957
974
  protected getWebSockets(): WebSocket[];
975
+ /**
976
+ * Force every connected client to reconnect and resync by closing all live
977
+ * WebSockets. Returns the number of sockets closed.
978
+ *
979
+ * Use this after an **out-of-band, server-side write** to the room's records
980
+ * — an admin import route, a migration script, a cron job, or a server
981
+ * action — that connected clients have no way to learn about. Without it, a
982
+ * browser tab holding stale in-memory state keeps operating on (and may
983
+ * autosave over) data that changed underneath it. Closing the socket makes
984
+ * the client SDK reconnect and pull fresh query results.
985
+ *
986
+ * The default close code is 1012 ("service restart") with reason
987
+ * 'state-refresh'. The DeepSpace client treats *any* close as a reconnect
988
+ * trigger (it does not special-case clean/1000 closes), so on reconnect it
989
+ * re-subscribes every active query and receives fresh `QUERY_RESULT`s —
990
+ * `useQuery` consumers see the new data without re-subscribing.
991
+ *
992
+ * Each close is guarded so one already-closing socket can't abort the sweep.
993
+ *
994
+ * @param options.code WebSocket close code (default 1012).
995
+ * @param options.reason WebSocket close reason (default 'state-refresh').
996
+ * @returns the number of sockets that were closed.
997
+ */
998
+ disconnectAllSockets(options?: {
999
+ code?: number;
1000
+ reason?: string;
1001
+ }): number;
958
1002
  /**
959
1003
  * Get the user attachment for a WebSocket.
960
1004
  */
@@ -1401,6 +1445,14 @@ declare class RecordRoom<E = Record<string, unknown>> extends BaseRoom<E> {
1401
1445
  * truthy value on the worker to opt in). Off by default.
1402
1446
  */
1403
1447
  private get perfLogEnabled();
1448
+ /**
1449
+ * The HTTP debug API (`/api/debug/*`) runs arbitrary SQL and role changes
1450
+ * with no auth of its own, so it is gated here at the DO's single ingress.
1451
+ * Off unless a deployment opts in with `ALLOW_DEBUG_ROUTES=true`
1452
+ * (`deepspace dev`/`test` set it automatically). Deployments holding shared
1453
+ * data override this to always return false.
1454
+ */
1455
+ protected get debugRoutesEnabled(): boolean;
1404
1456
  constructor(state: DurableObjectState, env: unknown, schemas?: CollectionSchema[], config?: RecordRoomConfig);
1405
1457
  private getPermissionContext;
1406
1458
  fetch(request: Request): Promise<Response>;
@@ -2955,7 +3007,7 @@ declare function createDeepSpaceAI(env: DeepSpaceAIEnv, provider: Provider, opti
2955
3007
  * const jwt = c.req.header('Authorization')!.slice(7)
2956
3008
  * const tools = await composioTools(c.env, { toolkit: 'gmail', authToken: jwt })
2957
3009
  * const ai = createDeepSpaceAI(c.env, 'anthropic', { authToken: jwt })
2958
- * const result = streamText({ model: ai('claude-sonnet-4-6'), prompt, tools })
3010
+ * const result = streamText({ model: ai('claude-sonnet-5'), prompt, tools })
2959
3011
  *
2960
3012
  * Merge with your own tools: `tools: { ...buildTools(executor), ...composio }`.
2961
3013
  */
package/dist/worker.js CHANGED
@@ -16,6 +16,8 @@ var BaseRoom = class {
16
16
  // ==========================================================================
17
17
  async fetch(request) {
18
18
  const url = new URL(request.url);
19
+ const internal = await this.handleInternalRequest(request, url);
20
+ if (internal) return internal;
19
21
  if (request.headers.get("Upgrade") === "websocket") {
20
22
  return this.handleWebSocketUpgrade(request, url);
21
23
  }
@@ -25,6 +27,36 @@ var BaseRoom = class {
25
27
  return new Response("Not Found", { status: 404 });
26
28
  }
27
29
  // ==========================================================================
30
+ // Internal Control-Plane Endpoints
31
+ // ==========================================================================
32
+ /**
33
+ * Handle built-in `/internal/*` control-plane routes shared by every room
34
+ * type. Returns a `Response` if the request was an internal route, or `null`
35
+ * to let the caller continue normal dispatch.
36
+ *
37
+ * Currently:
38
+ * - `POST /internal/disconnect-sockets` — close every live WebSocket so
39
+ * clients reconnect and resync. Optional JSON body `{ code?, reason? }`
40
+ * overrides the defaults (1012 / 'state-refresh'). Responds with
41
+ * `{ success: true, closed: <n> }`.
42
+ *
43
+ * See the security note on `fetch()`: this path is only reachable via DO
44
+ * stub fetch from the app worker, never from the public internet.
45
+ */
46
+ async handleInternalRequest(request, url) {
47
+ if (request.method === "POST" && url.pathname === "/internal/disconnect-sockets") {
48
+ let options;
49
+ try {
50
+ const text = await request.text();
51
+ if (text) options = JSON.parse(text);
52
+ } catch {
53
+ }
54
+ const closed = this.disconnectAllSockets(options);
55
+ return Response.json({ success: true, closed });
56
+ }
57
+ return null;
58
+ }
59
+ // ==========================================================================
28
60
  // WebSocket Connection
29
61
  // ==========================================================================
30
62
  async handleWebSocketUpgrade(request, url) {
@@ -111,6 +143,42 @@ var BaseRoom = class {
111
143
  getWebSockets() {
112
144
  return this.state.getWebSockets();
113
145
  }
146
+ /**
147
+ * Force every connected client to reconnect and resync by closing all live
148
+ * WebSockets. Returns the number of sockets closed.
149
+ *
150
+ * Use this after an **out-of-band, server-side write** to the room's records
151
+ * — an admin import route, a migration script, a cron job, or a server
152
+ * action — that connected clients have no way to learn about. Without it, a
153
+ * browser tab holding stale in-memory state keeps operating on (and may
154
+ * autosave over) data that changed underneath it. Closing the socket makes
155
+ * the client SDK reconnect and pull fresh query results.
156
+ *
157
+ * The default close code is 1012 ("service restart") with reason
158
+ * 'state-refresh'. The DeepSpace client treats *any* close as a reconnect
159
+ * trigger (it does not special-case clean/1000 closes), so on reconnect it
160
+ * re-subscribes every active query and receives fresh `QUERY_RESULT`s —
161
+ * `useQuery` consumers see the new data without re-subscribing.
162
+ *
163
+ * Each close is guarded so one already-closing socket can't abort the sweep.
164
+ *
165
+ * @param options.code WebSocket close code (default 1012).
166
+ * @param options.reason WebSocket close reason (default 'state-refresh').
167
+ * @returns the number of sockets that were closed.
168
+ */
169
+ disconnectAllSockets(options) {
170
+ const code = options?.code ?? 1012;
171
+ const reason = options?.reason ?? "state-refresh";
172
+ let closed = 0;
173
+ for (const ws of this.state.getWebSockets()) {
174
+ try {
175
+ ws.close(code, reason);
176
+ closed++;
177
+ } catch {
178
+ }
179
+ }
180
+ return closed;
181
+ }
114
182
  /**
115
183
  * Get the user attachment for a WebSocket.
116
184
  */
@@ -2670,6 +2738,16 @@ var RecordRoom = class extends BaseRoom {
2670
2738
  get perfLogEnabled() {
2671
2739
  return !!this.env?.DEEPSPACE_DO_PERF;
2672
2740
  }
2741
+ /**
2742
+ * The HTTP debug API (`/api/debug/*`) runs arbitrary SQL and role changes
2743
+ * with no auth of its own, so it is gated here at the DO's single ingress.
2744
+ * Off unless a deployment opts in with `ALLOW_DEBUG_ROUTES=true`
2745
+ * (`deepspace dev`/`test` set it automatically). Deployments holding shared
2746
+ * data override this to always return false.
2747
+ */
2748
+ get debugRoutesEnabled() {
2749
+ return this.env?.ALLOW_DEBUG_ROUTES === "true";
2750
+ }
2673
2751
  constructor(state, env, schemas = [], config = {}) {
2674
2752
  super(state, env);
2675
2753
  this.schemaRegistry = new SchemaRegistry([
@@ -2705,14 +2783,21 @@ var RecordRoom = class extends BaseRoom {
2705
2783
  // HTTP Entry Point (overrides BaseRoom.fetch)
2706
2784
  // ============================================================================
2707
2785
  async fetch(request) {
2786
+ const url = new URL(request.url);
2787
+ if (url.pathname.startsWith("/internal/")) {
2788
+ const internal = await this.handleInternalRequest(request, url);
2789
+ if (internal) return internal;
2790
+ }
2708
2791
  const fetchStart = Date.now();
2709
2792
  const isColdStart = this.freshConstruct;
2710
2793
  const needsInit = !this.initPromise;
2711
2794
  this.freshConstruct = false;
2712
2795
  await this.ensureInitialized();
2713
2796
  const initMs = Date.now() - fetchStart;
2714
- const url = new URL(request.url);
2715
2797
  if (url.pathname.startsWith("/api/")) {
2798
+ if (url.pathname.startsWith("/api/debug/") && !this.debugRoutesEnabled) {
2799
+ return new Response("Not Found", { status: 404 });
2800
+ }
2716
2801
  return handleApiRequest(this.createHandlerContext(), request, url);
2717
2802
  }
2718
2803
  if (request.headers.get("Upgrade") === "websocket") {