deepspace 0.4.5 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +90 -0
  2. package/dist/cli.js +3171 -1166
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.d.ts +31 -4
  5. package/dist/index.js +511 -366
  6. package/dist/index.js.map +1 -1
  7. package/dist/server.d.ts +27 -22
  8. package/dist/server.js +117 -94
  9. package/dist/server.js.map +1 -1
  10. package/dist/testing.js.map +1 -1
  11. package/dist/worker.d.ts +27 -22
  12. package/dist/worker.js +117 -94
  13. package/dist/worker.js.map +1 -1
  14. package/features/admin-page/src/AdminPage.tsx +3 -5
  15. package/features/canvas/src/CanvasEditorPage.tsx +0 -2
  16. package/features/canvas/src/components/CanvasView.tsx +1 -1
  17. package/features/docs/src/LibrarySidebar.tsx +7 -1
  18. package/features/docs/src/use-yjs-room-with-awareness.ts +4 -0
  19. package/features/file-attachments/src/AttachmentsPage.tsx +7 -1
  20. package/features/file-attachments/src/useFilePreview.ts +7 -5
  21. package/features/file-manager/src/FileManagerPage.tsx +2 -3
  22. package/features/integration-test/feature.json +10 -3
  23. package/features/integration-test/src/IntegrationTestPage.tsx +15 -12
  24. package/features/landing/src/LandingPage.tsx +1 -1
  25. package/features/landing/src/sections/FeaturesGridSection.tsx +0 -92
  26. package/features/messaging/src/components/chat/MessageItem.tsx +5 -1
  27. package/features/messaging/src/hooks/useChatChannel.ts +5 -1
  28. package/features/presence-test/feature.json +2 -1
  29. package/features/sidebar/src/AppSidebar.tsx +1 -1
  30. package/features/tasks/src/TasksPage.tsx +1 -1
  31. package/features/testing/feature.json +2 -1
  32. package/features/testing/src/TestPage.tsx +8 -8
  33. package/package.json +4 -4
  34. package/scripts/add-feature.cjs +277 -272
  35. package/dist/testing.d.ts +0 -115
package/dist/server.d.ts CHANGED
@@ -488,10 +488,10 @@ interface CronContext {
488
488
  query(collection: string, opts?: {
489
489
  where?: Record<string, unknown>;
490
490
  limit?: number;
491
- }): Promise<any[]>;
492
- create(collection: string, data: Record<string, unknown>): Promise<any>;
493
- update(collection: string, recordId: string, data: Record<string, unknown>): Promise<any>;
494
- delete(collection: string, recordId: string): Promise<any>;
491
+ }): Promise<unknown[]>;
492
+ create(collection: string, data: Record<string, unknown>): Promise<unknown>;
493
+ update(collection: string, recordId: string, data: Record<string, unknown>): Promise<unknown>;
494
+ delete(collection: string, recordId: string): Promise<unknown>;
495
495
  };
496
496
  /**
497
497
  * Call a platform integration endpoint (e.g. "openai/chat-completion",
@@ -499,7 +499,7 @@ interface CronContext {
499
499
  * APP_OWNER_JWT the rest of the SDK uses for server-side billed calls.
500
500
  */
501
501
  integrations: {
502
- call(endpoint: string, params?: Record<string, unknown>): Promise<any>;
502
+ call(endpoint: string, params?: Record<string, unknown>): Promise<unknown>;
503
503
  };
504
504
  /** App owner's user ID */
505
505
  ownerUserId: string;
@@ -1390,7 +1390,7 @@ declare abstract class BaseRoom<E = Record<string, unknown>> {
1390
1390
  fetch(request: Request): Promise<Response>;
1391
1391
  private handleWebSocketUpgrade;
1392
1392
  webSocketMessage(ws: WebSocket, message: ArrayBuffer | string): Promise<void>;
1393
- webSocketClose(ws: WebSocket, code: number, reason: string): Promise<void>;
1393
+ webSocketClose(ws: WebSocket, _code: number, _reason: string): Promise<void>;
1394
1394
  webSocketError(ws: WebSocket, error: unknown): Promise<void>;
1395
1395
  alarm(): Promise<void>;
1396
1396
  /**
@@ -1398,7 +1398,7 @@ declare abstract class BaseRoom<E = Record<string, unknown>> {
1398
1398
  * Return an augmented attachment to serialize on the WebSocket,
1399
1399
  * or void to use the default attachment.
1400
1400
  */
1401
- protected onConnect(ws: WebSocket, user: UserAttachment): UserAttachment | void | Promise<UserAttachment | void>;
1401
+ protected onConnect(_ws: WebSocket, _user: UserAttachment): UserAttachment | void | Promise<UserAttachment | void>;
1402
1402
  /**
1403
1403
  * Called for each parsed JSON message.
1404
1404
  */
@@ -1413,7 +1413,7 @@ declare abstract class BaseRoom<E = Record<string, unknown>> {
1413
1413
  /**
1414
1414
  * Called when a WebSocket disconnects.
1415
1415
  */
1416
- protected onDisconnect(ws: WebSocket, user: UserAttachment): void | Promise<void>;
1416
+ protected onDisconnect(_ws: WebSocket, _user: UserAttachment): void | Promise<void>;
1417
1417
  /**
1418
1418
  * Called for HTTP requests that are NOT WebSocket upgrades.
1419
1419
  */
@@ -1670,7 +1670,6 @@ declare class SchemaRegistry {
1670
1670
  registerTrusted(schema: CollectionSchema): void;
1671
1671
  get(name: string): CollectionSchema | undefined;
1672
1672
  has(name: string): boolean;
1673
- hasTrusted(name: string): boolean;
1674
1673
  all(): CollectionSchema[];
1675
1674
  names(): string[];
1676
1675
  }
@@ -1742,6 +1741,12 @@ declare class RecordRoom<E = Record<string, unknown>> extends BaseRoom<E> {
1742
1741
  private ownerUserId;
1743
1742
  /** True until the first fetch() completes — detects hibernation wake-up */
1744
1743
  private freshConstruct;
1744
+ /**
1745
+ * Per-connection `[DO Perf]` timing logs are noisy on every hot path, so
1746
+ * they're gated behind a `DEEPSPACE_DO_PERF` env binding (set it to any
1747
+ * truthy value on the worker to opt in). Off by default.
1748
+ */
1749
+ private get perfLogEnabled();
1745
1750
  constructor(state: DurableObjectState, env: unknown, schemas?: CollectionSchema[], config?: RecordRoomConfig);
1746
1751
  private getPermissionContext;
1747
1752
  fetch(request: Request): Promise<Response>;
@@ -1754,12 +1759,13 @@ declare class RecordRoom<E = Record<string, unknown>> extends BaseRoom<E> {
1754
1759
  private ensureCollectionTable;
1755
1760
  private ensureAllCollectionTables;
1756
1761
  protected onConnect(ws: WebSocket, user: UserAttachment): Promise<ConnectionAttachment>;
1757
- protected onMessage(ws: WebSocket, user: UserAttachment, msg: {
1758
- type: string;
1759
- [key: string]: unknown;
1760
- }): Promise<void>;
1761
- protected onBinaryMessage(ws: WebSocket, user: UserAttachment, data: ArrayBuffer): Promise<void>;
1762
- protected onDisconnect(ws: WebSocket, user: UserAttachment): void;
1762
+ /**
1763
+ * Required to satisfy BaseRoom's abstract contract, but never invoked:
1764
+ * RecordRoom overrides `webSocketMessage`/`webSocketClose` directly (below),
1765
+ * so the BaseRoom dispatch path that would call this never runs. All real
1766
+ * message handling lives in `handleRecordMessage`.
1767
+ */
1768
+ protected onMessage(): void;
1763
1769
  webSocketMessage(ws: WebSocket, message: ArrayBuffer | string): Promise<void>;
1764
1770
  webSocketClose(ws: WebSocket, code: number, reason: string): Promise<void>;
1765
1771
  webSocketError(ws: WebSocket, error: unknown): Promise<void>;
@@ -1803,12 +1809,12 @@ declare class YjsRoom<E = Record<string, unknown>> extends BaseRoom<E> {
1803
1809
  private getDoc;
1804
1810
  private persistDoc;
1805
1811
  protected onConnect(ws: WebSocket, user: UserAttachment): YjsAttachment;
1806
- protected onMessage(ws: WebSocket, user: UserAttachment, message: {
1812
+ protected onMessage(_ws: WebSocket, _user: UserAttachment, _message: {
1807
1813
  type: string;
1808
1814
  [key: string]: unknown;
1809
1815
  }): void;
1810
1816
  protected onBinaryMessage(ws: WebSocket, user: UserAttachment, data: ArrayBuffer): void;
1811
- protected onDisconnect(ws: WebSocket, user: UserAttachment): void;
1817
+ protected onDisconnect(ws: WebSocket, _user: UserAttachment): void;
1812
1818
  private handleSync;
1813
1819
  private handleAwareness;
1814
1820
  private readAwarenessUpdates;
@@ -1890,13 +1896,13 @@ declare abstract class GameRoom<E = Record<string, unknown>> extends BaseRoom<E>
1890
1896
  */
1891
1897
  protected abstract onTick(state: Record<string, unknown>, inputs: GameInput[], tick: number): Record<string, unknown> | undefined | Promise<Record<string, unknown> | undefined>;
1892
1898
  /** Called when a player connects */
1893
- protected onPlayerJoin(player: Player): void;
1899
+ protected onPlayerJoin(_player: Player): void;
1894
1900
  /** Called when a player disconnects */
1895
- protected onPlayerLeave(player: Player): void;
1901
+ protected onPlayerLeave(_player: Player): void;
1896
1902
  /** Called when the game starts */
1897
1903
  protected onGameStart(): void;
1898
1904
  /** Called when the game ends */
1899
- protected onGameEnd(finalState: Record<string, unknown>): void;
1905
+ protected onGameEnd(_finalState: Record<string, unknown>): void;
1900
1906
  /**
1901
1907
  * Called once when the DO first hydrates persisted state from storage.
1902
1908
  * Receives the parsed state blob as it was written by a previous build.
@@ -3005,8 +3011,7 @@ interface VerifiedAuth {
3005
3011
  userId: string;
3006
3012
  claims: JwtClaims;
3007
3013
  }
3008
- interface VerifyResult extends VerifiedAuth {
3009
- }
3014
+ type VerifyResult = VerifiedAuth;
3010
3015
  interface TokenDebugInfo {
3011
3016
  iss?: string | null;
3012
3017
  aud?: string | string[] | null;
package/dist/server.js CHANGED
@@ -475,7 +475,8 @@ function buildCronContext(env, ownerUserId, roomId = "default") {
475
475
  if (opts?.where) params.where = opts.where;
476
476
  if (opts?.limit) params.limit = opts.limit;
477
477
  const data = await executeTool3("records.query", params);
478
- return data.records || [];
478
+ const records2 = data?.records;
479
+ return records2 ?? [];
479
480
  },
480
481
  async create(collection, data) {
481
482
  return executeTool3("records.create", { collection, data });
@@ -632,7 +633,8 @@ async function composioTools(env, options) {
632
633
  if (!list.ok || !list.success) {
633
634
  throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`);
634
635
  }
635
- const items = Array.isArray(list.data?.items) ? list.data.items : [];
636
+ const listItems = list.data?.items;
637
+ const items = Array.isArray(listItems) ? listItems : [];
636
638
  const tools = {};
637
639
  for (const item of items) {
638
640
  if (!item?.slug) continue;
@@ -1333,7 +1335,7 @@ var BaseRoom = class {
1333
1335
  console.error(`[${this.constructor.name}] Message error:`, e);
1334
1336
  }
1335
1337
  }
1336
- async webSocketClose(ws, code, reason) {
1338
+ async webSocketClose(ws, _code, _reason) {
1337
1339
  const attachment = ws.deserializeAttachment();
1338
1340
  if (attachment) {
1339
1341
  try {
@@ -1358,12 +1360,12 @@ var BaseRoom = class {
1358
1360
  * Return an augmented attachment to serialize on the WebSocket,
1359
1361
  * or void to use the default attachment.
1360
1362
  */
1361
- onConnect(ws, user) {
1363
+ onConnect(_ws, _user) {
1362
1364
  }
1363
1365
  /**
1364
1366
  * Called when a WebSocket disconnects.
1365
1367
  */
1366
- onDisconnect(ws, user) {
1368
+ onDisconnect(_ws, _user) {
1367
1369
  }
1368
1370
  // ==========================================================================
1369
1371
  // Utilities
@@ -1611,7 +1613,7 @@ function coerceValue(value, storage, interpretation) {
1611
1613
  if (m) return parseFloat(m[1]) / 100;
1612
1614
  }
1613
1615
  if (interpretation.kind === "currency") {
1614
- const cleaned2 = s.replace(/[^0-9.\-]/g, "");
1616
+ const cleaned2 = s.replace(/[^0-9.-]/g, "");
1615
1617
  const n2 = parseFloat(cleaned2);
1616
1618
  if (!isNaN(n2)) return n2;
1617
1619
  return s;
@@ -1801,9 +1803,6 @@ var SchemaRegistry = class {
1801
1803
  has(name) {
1802
1804
  return this.trusted.has(name);
1803
1805
  }
1804
- hasTrusted(name) {
1805
- return this.trusted.has(name);
1806
- }
1807
1806
  all() {
1808
1807
  return Array.from(this.trusted.values());
1809
1808
  }
@@ -2118,7 +2117,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
2118
2117
  const columns = getResolvedColumns(schema);
2119
2118
  const existing = getRecord(ctx.sql, collection, recordId, schema);
2120
2119
  const isUpdate = existing !== null;
2121
- let mergedData = isUpdate ? { ...existing.data, ...data } : { ...data };
2120
+ const mergedData = isUpdate ? { ...existing.data, ...data } : { ...data };
2122
2121
  for (const col of columns) {
2123
2122
  if (col.readonly) continue;
2124
2123
  if (!isUpdate) {
@@ -2173,7 +2172,10 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
2173
2172
  const permCtx = ctx.getPermissionContext();
2174
2173
  if (isUpdate) {
2175
2174
  if (!canUpdate(schema, userRole, { ...existing, recordId }, userId, permCtx)) {
2176
- return { success: false, error: `UPDATE DENIED: role=${userRole}, collection=${collection}` };
2175
+ return {
2176
+ success: false,
2177
+ error: `UPDATE DENIED: role=${userRole}, collection=${collection}`
2178
+ };
2177
2179
  }
2178
2180
  const fieldError = checkFieldPermissions(schema, userRole, finalData, existing.data);
2179
2181
  if (fieldError) {
@@ -2181,7 +2183,10 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
2181
2183
  }
2182
2184
  } else {
2183
2185
  if (!canCreate(schema, userRole)) {
2184
- return { success: false, error: `CREATE DENIED: role=${userRole}, collection=${collection}` };
2186
+ return {
2187
+ success: false,
2188
+ error: `CREATE DENIED: role=${userRole}, collection=${collection}`
2189
+ };
2185
2190
  }
2186
2191
  }
2187
2192
  }
@@ -2203,7 +2208,10 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
2203
2208
  );
2204
2209
  if (existing2.toArray().length > 0) {
2205
2210
  const fields = schema.uniqueOn.map((f) => `${f}=${finalData[f] ?? "null"}`).join(", ");
2206
- return { success: false, error: `Duplicate: a record with ${fields} already exists in ${collection}` };
2211
+ return {
2212
+ success: false,
2213
+ error: `Duplicate: a record with ${fields} already exists in ${collection}`
2214
+ };
2207
2215
  }
2208
2216
  }
2209
2217
  if (isUpdate) {
@@ -2217,13 +2225,16 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
2217
2225
  ctx.sql.exec(`UPDATE "${tbl}" SET ${setClauses.join(", ")} WHERE _row_id = ?`, ...params);
2218
2226
  } else {
2219
2227
  const colIds = Object.keys(colValues);
2220
- const allCols = ["_row_id", "_created_by", "_created_at", "_updated_at", ...colIds.map((c) => `"${c}"`)];
2228
+ const allCols = [
2229
+ "_row_id",
2230
+ "_created_by",
2231
+ "_created_at",
2232
+ "_updated_at",
2233
+ ...colIds.map((c) => `"${c}"`)
2234
+ ];
2221
2235
  const placeholders = allCols.map(() => "?").join(", ");
2222
2236
  const params = [recordId, userId, now, now, ...colIds.map((c) => colValues[c])];
2223
- ctx.sql.exec(
2224
- `INSERT INTO "${tbl}" (${allCols.join(", ")}) VALUES (${placeholders})`,
2225
- ...params
2226
- );
2237
+ ctx.sql.exec(`INSERT INTO "${tbl}" (${allCols.join(", ")}) VALUES (${placeholders})`, ...params);
2227
2238
  }
2228
2239
  const saved = getRecord(ctx.sql, collection, recordId, schema);
2229
2240
  const record = {
@@ -3456,9 +3467,21 @@ var RecordRoom = class extends BaseRoom {
3456
3467
  ownerUserId;
3457
3468
  /** True until the first fetch() completes — detects hibernation wake-up */
3458
3469
  freshConstruct = true;
3470
+ /**
3471
+ * Per-connection `[DO Perf]` timing logs are noisy on every hot path, so
3472
+ * they're gated behind a `DEEPSPACE_DO_PERF` env binding (set it to any
3473
+ * truthy value on the worker to opt in). Off by default.
3474
+ */
3475
+ get perfLogEnabled() {
3476
+ return !!this.env?.DEEPSPACE_DO_PERF;
3477
+ }
3459
3478
  constructor(state, env, schemas = [], config = {}) {
3460
3479
  super(state, env);
3461
- this.schemaRegistry = new SchemaRegistry([...SYSTEM_COLLECTION_SCHEMAS, BASE_USERS_SCHEMA, ...schemas]);
3480
+ this.schemaRegistry = new SchemaRegistry([
3481
+ ...SYSTEM_COLLECTION_SCHEMAS,
3482
+ BASE_USERS_SCHEMA,
3483
+ ...schemas
3484
+ ]);
3462
3485
  this.ownerUserId = config.ownerUserId ?? null;
3463
3486
  const wsCount = this.state.getWebSockets().length;
3464
3487
  if (wsCount > 0) {
@@ -3542,10 +3565,7 @@ var RecordRoom = class extends BaseRoom {
3542
3565
  const now = (/* @__PURE__ */ new Date()).toISOString();
3543
3566
  for (const row of oldUsers) {
3544
3567
  const r = row;
3545
- const existing = this.sql.exec(
3546
- `SELECT 1 FROM c_users WHERE _row_id = ?`,
3547
- r.user_id
3548
- ).toArray();
3568
+ const existing = this.sql.exec(`SELECT 1 FROM c_users WHERE _row_id = ?`, r.user_id).toArray();
3549
3569
  if (existing.length > 0) continue;
3550
3570
  this.sql.exec(
3551
3571
  `INSERT INTO c_users (_row_id, _created_by, _created_at, _updated_at, col_email, col_name, col_imageurl, col_role, col_createdat, col_lastseenat) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -3579,7 +3599,6 @@ var RecordRoom = class extends BaseRoom {
3579
3599
  }
3580
3600
  let migrated = 0;
3581
3601
  let skipped = 0;
3582
- const now = (/* @__PURE__ */ new Date()).toISOString();
3583
3602
  for (const row of rows) {
3584
3603
  const r = row;
3585
3604
  const schema = this.schemaRegistry.get(r.collection);
@@ -3588,17 +3607,11 @@ var RecordRoom = class extends BaseRoom {
3588
3607
  continue;
3589
3608
  }
3590
3609
  const tbl = collectionTableName(r.collection);
3591
- const tblExists = this.sql.exec(
3592
- `SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
3593
- tbl
3594
- ).toArray().length > 0;
3610
+ const tblExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`, tbl).toArray().length > 0;
3595
3611
  if (!tblExists) {
3596
3612
  this.ensureCollectionTable(schema);
3597
3613
  }
3598
- const existing = this.sql.exec(
3599
- `SELECT 1 FROM "${tbl}" WHERE _row_id = ?`,
3600
- r.record_id
3601
- ).toArray();
3614
+ const existing = this.sql.exec(`SELECT 1 FROM "${tbl}" WHERE _row_id = ?`, r.record_id).toArray();
3602
3615
  if (existing.length > 0) {
3603
3616
  skipped++;
3604
3617
  continue;
@@ -3613,9 +3626,21 @@ var RecordRoom = class extends BaseRoom {
3613
3626
  const columns = schema.columns.map(resolveColumn);
3614
3627
  const colValues = dataToColumnValues(data, columns);
3615
3628
  const colIds = Object.keys(colValues);
3616
- const allCols = ["_row_id", "_created_by", "_created_at", "_updated_at", ...colIds.map((c) => `"${c}"`)];
3629
+ const allCols = [
3630
+ "_row_id",
3631
+ "_created_by",
3632
+ "_created_at",
3633
+ "_updated_at",
3634
+ ...colIds.map((c) => `"${c}"`)
3635
+ ];
3617
3636
  const placeholders = allCols.map(() => "?").join(", ");
3618
- const params = [r.record_id, r.created_by, r.created_at, r.updated_at, ...colIds.map((c) => colValues[c])];
3637
+ const params = [
3638
+ r.record_id,
3639
+ r.created_by,
3640
+ r.created_at,
3641
+ r.updated_at,
3642
+ ...colIds.map((c) => colValues[c])
3643
+ ];
3619
3644
  this.sql.exec(
3620
3645
  `INSERT INTO "${tbl}" (${allCols.join(", ")}) VALUES (${placeholders})`,
3621
3646
  ...params
@@ -3623,7 +3648,9 @@ var RecordRoom = class extends BaseRoom {
3623
3648
  migrated++;
3624
3649
  }
3625
3650
  if (migrated > 0 || skipped > 0) {
3626
- console.log(`[RecordRoom] Migrated ${migrated} records from document-mode, ${skipped} skipped`);
3651
+ console.log(
3652
+ `[RecordRoom] Migrated ${migrated} records from document-mode, ${skipped} skipped`
3653
+ );
3627
3654
  }
3628
3655
  this.sql.exec(`DROP TABLE IF EXISTS records`);
3629
3656
  } catch (error) {
@@ -3646,7 +3673,12 @@ var RecordRoom = class extends BaseRoom {
3646
3673
  // ============================================================================
3647
3674
  async onConnect(ws, user) {
3648
3675
  await this.ensureInitialized();
3649
- const timing = this._fetchTiming ?? { fetchStart: Date.now(), isColdStart: false, needsInit: false, initMs: 0 };
3676
+ const timing = this._fetchTiming ?? {
3677
+ fetchStart: Date.now(),
3678
+ isColdStart: false,
3679
+ needsInit: false,
3680
+ initMs: 0
3681
+ };
3650
3682
  const userId = user.userId;
3651
3683
  const userName = user.userName;
3652
3684
  const userEmail = user.userEmail;
@@ -3657,7 +3689,16 @@ var RecordRoom = class extends BaseRoom {
3657
3689
  const isOwner2 = this.ownerUserId != null && userId === this.ownerUserId;
3658
3690
  const regStart = Date.now();
3659
3691
  const defaultRole = this.schemaRegistry.get("users")?.defaultRole ?? "member";
3660
- const registeredUser = await registerUser(this.sql, userId, userName, userEmail, userImageUrl, isOwner2, defaultRole, this.schemaRegistry);
3692
+ const registeredUser = await registerUser(
3693
+ this.sql,
3694
+ userId,
3695
+ userName,
3696
+ userEmail,
3697
+ userImageUrl,
3698
+ isOwner2,
3699
+ defaultRole,
3700
+ this.schemaRegistry
3701
+ );
3661
3702
  const regMs = Date.now() - regStart;
3662
3703
  attachment = {
3663
3704
  userId: registeredUser.id,
@@ -3671,7 +3712,10 @@ var RecordRoom = class extends BaseRoom {
3671
3712
  };
3672
3713
  this.send(ws, { type: MSG.USER_INFO, payload: registeredUser });
3673
3714
  const totalMs = Date.now() - timing.fetchStart;
3674
- console.log(`[DO Perf] cold=${timing.isColdStart} | init: ${timing.initMs}ms reg: ${regMs}ms | total: ${totalMs}ms`);
3715
+ if (this.perfLogEnabled)
3716
+ console.log(
3717
+ `[DO Perf] cold=${timing.isColdStart} | init: ${timing.initMs}ms reg: ${regMs}ms | total: ${totalMs}ms`
3718
+ );
3675
3719
  } else {
3676
3720
  attachment = {
3677
3721
  userId,
@@ -3682,63 +3726,37 @@ var RecordRoom = class extends BaseRoom {
3682
3726
  yjsSubscriptions: [],
3683
3727
  yjsClientId: this.nextYjsClientId++
3684
3728
  };
3685
- this.send(ws, { type: MSG.USER_INFO, payload: {
3686
- id: userId,
3687
- name: "Anonymous",
3688
- email: "",
3689
- role: ROLE_ANONYMOUS,
3690
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3691
- lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
3692
- } });
3729
+ this.send(ws, {
3730
+ type: MSG.USER_INFO,
3731
+ payload: {
3732
+ id: userId,
3733
+ name: "Anonymous",
3734
+ email: "",
3735
+ role: ROLE_ANONYMOUS,
3736
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3737
+ lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
3738
+ }
3739
+ });
3693
3740
  const totalMs = Date.now() - timing.fetchStart;
3694
- console.log(`[DO Perf] cold=${timing.isColdStart} anon | total: ${totalMs}ms`);
3741
+ if (this.perfLogEnabled)
3742
+ console.log(`[DO Perf] cold=${timing.isColdStart} anon | total: ${totalMs}ms`);
3695
3743
  }
3696
3744
  this._fetchTiming = null;
3697
3745
  return attachment;
3698
3746
  }
3699
- async onMessage(ws, user, msg) {
3700
- await this.ensureInitialized();
3701
- const attachment = ws.deserializeAttachment();
3702
- if (!attachment) {
3703
- this.send(ws, { type: MSG.ERROR, payload: { error: "Connection not found" } });
3704
- return;
3705
- }
3706
- try {
3707
- await this.handleRecordMessage(ws, attachment, msg);
3708
- } catch (e) {
3709
- const errMsg = e instanceof Error ? e.message : String(e);
3710
- console.error(`[RecordRoom] Message handler error: ${errMsg}`);
3711
- this.send(ws, { type: MSG.ERROR, payload: { error: `Invalid message: ${errMsg}` } });
3712
- }
3713
- }
3714
- async onBinaryMessage(ws, user, data) {
3715
- await this.ensureInitialized();
3716
- const attachment = ws.deserializeAttachment();
3717
- if (!attachment) return;
3718
- try {
3719
- await handleYjsBinaryMessage(
3720
- this.createYjsContext(),
3721
- ws,
3722
- attachment,
3723
- new Uint8Array(data)
3724
- );
3725
- } catch (e) {
3726
- console.error("[RecordRoom] Yjs binary message error:", e);
3727
- }
3728
- }
3729
- onDisconnect(ws, user) {
3730
- console.log(`[RecordRoom] webSocketClose`);
3731
- try {
3732
- const attachment = ws.deserializeAttachment();
3733
- if (attachment) {
3734
- broadcastAwarenessRemoval(this.createYjsContext(), attachment);
3735
- }
3736
- } catch {
3737
- }
3747
+ /**
3748
+ * Required to satisfy BaseRoom's abstract contract, but never invoked:
3749
+ * RecordRoom overrides `webSocketMessage`/`webSocketClose` directly (below),
3750
+ * so the BaseRoom dispatch path that would call this never runs. All real
3751
+ * message handling lives in `handleRecordMessage`.
3752
+ */
3753
+ onMessage() {
3754
+ throw new Error("RecordRoom.onMessage is unreachable; use webSocketMessage");
3738
3755
  }
3739
3756
  // ============================================================================
3740
- // Hibernation API (delegates to BaseRoom via super, but we need
3741
- // to ensure webSocketMessage gets the ensureInitialized treatment)
3757
+ // Hibernation API — RecordRoom overrides these directly (rather than using
3758
+ // BaseRoom's onMessage/onBinaryMessage/onDisconnect hooks) so it can run
3759
+ // ensureInitialized() before every message after a hibernation wake-up.
3742
3760
  // ============================================================================
3743
3761
  async webSocketMessage(ws, message) {
3744
3762
  await this.ensureInitialized();
@@ -3809,7 +3827,12 @@ var RecordRoom = class extends BaseRoom {
3809
3827
  handleUserList(userCtx, ws, attachment);
3810
3828
  break;
3811
3829
  case MSG.USER_UPDATE:
3812
- handleUserUpdate(recordCtx, ws, attachment, payload);
3830
+ handleUserUpdate(
3831
+ recordCtx,
3832
+ ws,
3833
+ attachment,
3834
+ payload
3835
+ );
3813
3836
  break;
3814
3837
  case MSG.SET_ROLE:
3815
3838
  await handleSetRole(userCtx, ws, attachment, payload);
@@ -3975,7 +3998,7 @@ var YjsRoom = class extends BaseRoom {
3975
3998
  this.sendAwarenessSnapshot(ws);
3976
3999
  return attachment;
3977
4000
  }
3978
- onMessage(ws, user, message) {
4001
+ onMessage(_ws, _user, _message) {
3979
4002
  }
3980
4003
  onBinaryMessage(ws, user, data) {
3981
4004
  const bytes = new Uint8Array(data);
@@ -3987,7 +4010,7 @@ var YjsRoom = class extends BaseRoom {
3987
4010
  this.handleAwareness(ws, decoder, bytes);
3988
4011
  }
3989
4012
  }
3990
- onDisconnect(ws, user) {
4013
+ onDisconnect(ws, _user) {
3991
4014
  const attachment = ws.deserializeAttachment();
3992
4015
  if (attachment?.awarenessClientId == null) return;
3993
4016
  const clientId = attachment.awarenessClientId;
@@ -4348,16 +4371,16 @@ var GameRoom = class extends BaseRoom {
4348
4371
  return this.currentTick;
4349
4372
  }
4350
4373
  /** Called when a player connects */
4351
- onPlayerJoin(player) {
4374
+ onPlayerJoin(_player) {
4352
4375
  }
4353
4376
  /** Called when a player disconnects */
4354
- onPlayerLeave(player) {
4377
+ onPlayerLeave(_player) {
4355
4378
  }
4356
4379
  /** Called when the game starts */
4357
4380
  onGameStart() {
4358
4381
  }
4359
4382
  /** Called when the game ends */
4360
- onGameEnd(finalState) {
4383
+ onGameEnd(_finalState) {
4361
4384
  }
4362
4385
  /**
4363
4386
  * Called once when the DO first hydrates persisted state from storage.