deepspace 0.4.4 → 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 (34) hide show
  1. package/README.md +90 -0
  2. package/dist/cli.js +2912 -1984
  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 +29 -108
  8. package/dist/server.js +126 -589
  9. package/dist/server.js.map +1 -1
  10. package/dist/testing.js.map +1 -1
  11. package/dist/worker.d.ts +29 -108
  12. package/dist/worker.js +126 -589
  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
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,19 +3998,19 @@ 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
- const bytes2 = new Uint8Array(data);
3982
- const decoder = createDecoder(bytes2);
4004
+ const bytes = new Uint8Array(data);
4005
+ const decoder = createDecoder(bytes);
3983
4006
  const messageType = readVarUint(decoder);
3984
4007
  if (messageType === MSG_SYNC) {
3985
- this.handleSync(ws, decoder, bytes2);
4008
+ this.handleSync(ws, decoder, bytes);
3986
4009
  } else if (messageType === MSG_AWARENESS) {
3987
- this.handleAwareness(ws, decoder, bytes2);
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.
@@ -5189,319 +5212,6 @@ function cronMatches(parsed, timezone, date) {
5189
5212
  return parsed.minute.has(minute) && parsed.hour.has(hour) && parsed.dayOfMonth.has(dayOfMonth) && parsed.month.has(month) && parsed.dayOfWeek.has(dayOfWeek);
5190
5213
  }
5191
5214
 
5192
- // src/server/rooms/secrets-room.ts
5193
- var INTERNAL_HEADER = "x-deepspace-internal";
5194
- var INTERNAL_VALUE = "app-secrets";
5195
- var VERSION = "app-secret-v1";
5196
- var NAME_INFO = "deepspace app secret name v1";
5197
- var VALUE_INFO = "deepspace app secret value v1";
5198
- var HMAC_INFO = "deepspace app secret lookup v1";
5199
- var STORE_ID = "default";
5200
- var SecretsRoom = class extends BaseRoom {
5201
- initialized = false;
5202
- onMessage(_ws, _user, _message) {
5203
- }
5204
- async fetch(request) {
5205
- this.ensureInitialized();
5206
- return super.fetch(request);
5207
- }
5208
- async onRequest(request) {
5209
- if (request.headers.get(INTERNAL_HEADER) !== INTERNAL_VALUE) {
5210
- return json({ error: "Not found" }, 404);
5211
- }
5212
- const url = new URL(request.url);
5213
- try {
5214
- if (request.method === "GET" && url.pathname.endsWith("/list")) {
5215
- return json({ version: this.storeVersion(), secrets: await this.list() });
5216
- }
5217
- if (request.method === "GET" && url.pathname.endsWith("/pull")) {
5218
- return json({ version: this.storeVersion(), secrets: await this.pull() });
5219
- }
5220
- if (request.method === "GET" && url.pathname.endsWith("/version")) {
5221
- return json({ version: this.storeVersion() });
5222
- }
5223
- if (request.method === "PATCH" && url.pathname.endsWith("/patch")) {
5224
- const body = await request.json();
5225
- const result = await this.patch(body);
5226
- return json(result);
5227
- }
5228
- if (request.method === "PUT" && url.pathname.endsWith("/replace")) {
5229
- const body = await request.json();
5230
- const result = await this.replace(body);
5231
- return json(result);
5232
- }
5233
- } catch (err) {
5234
- const message = err instanceof Error ? err.message : String(err);
5235
- const status = message.includes("conflict") ? 409 : 500;
5236
- return json({ error: message }, status);
5237
- }
5238
- return json({ error: "Not found" }, 404);
5239
- }
5240
- async list() {
5241
- const rows = this.sql.exec(
5242
- `SELECT name_encrypted, version, updated_by, created_at, updated_at
5243
- FROM app_secrets_store ORDER BY updated_at DESC`
5244
- ).toArray();
5245
- const out = [];
5246
- for (const row of rows) {
5247
- out.push({
5248
- name: await decryptText(this.requireKey(), row.name_encrypted, NAME_INFO),
5249
- version: row.version,
5250
- createdAt: row.created_at,
5251
- updatedAt: row.updated_at,
5252
- updatedBy: row.updated_by
5253
- });
5254
- }
5255
- return out.sort((a, b) => a.name.localeCompare(b.name));
5256
- }
5257
- async pull() {
5258
- const rows = this.sql.exec(
5259
- `SELECT name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at
5260
- FROM app_secrets_store`
5261
- ).toArray();
5262
- const out = {};
5263
- for (const row of rows) {
5264
- const name = await decryptText(this.requireKey(), row.name_encrypted, NAME_INFO);
5265
- out[name] = await decryptText(this.requireKey(), row.value_encrypted, VALUE_INFO);
5266
- }
5267
- return out;
5268
- }
5269
- async patch(body) {
5270
- const now = (/* @__PURE__ */ new Date()).toISOString();
5271
- const actor = body.updatedBy ?? null;
5272
- const key = this.requireKey();
5273
- const setEntries = Object.entries(body.set ?? {});
5274
- const deleteNames = body.delete ?? [];
5275
- for (const name of [...setEntries.map(([name2]) => name2), ...deleteNames]) {
5276
- validateSecretName(name);
5277
- }
5278
- const writes = [];
5279
- for (const [name, value] of setEntries) {
5280
- const nameHash = await hashName(key, name);
5281
- const existing = this.sql.exec(
5282
- `SELECT version, created_at FROM app_secrets_store WHERE name_hash = ?`,
5283
- nameHash
5284
- ).toArray()[0];
5285
- writes.push({
5286
- nameHash,
5287
- nameEncrypted: await encryptText(key, name, NAME_INFO),
5288
- valueEncrypted: await encryptText(key, value, VALUE_INFO),
5289
- version: (existing?.version ?? 0) + 1,
5290
- createdAt: existing?.created_at ?? now
5291
- });
5292
- }
5293
- const deletes = [];
5294
- for (const name of deleteNames) {
5295
- deletes.push(await hashName(key, name));
5296
- }
5297
- this.transaction(() => {
5298
- for (const write of writes) {
5299
- this.sql.exec(
5300
- `INSERT OR REPLACE INTO app_secrets_store
5301
- (name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
5302
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
5303
- write.nameHash,
5304
- write.nameEncrypted,
5305
- write.valueEncrypted,
5306
- write.version,
5307
- actor,
5308
- write.createdAt,
5309
- now
5310
- );
5311
- }
5312
- for (const nameHash of deletes) {
5313
- this.sql.exec(`DELETE FROM app_secrets_store WHERE name_hash = ?`, nameHash);
5314
- }
5315
- this.bumpStoreVersion(now);
5316
- });
5317
- return { version: this.storeVersion(), secrets: await this.list() };
5318
- }
5319
- async replace(body) {
5320
- const next = body.secrets ?? {};
5321
- for (const name of Object.keys(next)) validateSecretName(name);
5322
- const now = (/* @__PURE__ */ new Date()).toISOString();
5323
- const actor = body.updatedBy ?? null;
5324
- const key = this.requireKey();
5325
- const writes = [];
5326
- for (const [name, value] of Object.entries(next)) {
5327
- writes.push({
5328
- nameHash: await hashName(key, name),
5329
- nameEncrypted: await encryptText(key, name, NAME_INFO),
5330
- valueEncrypted: await encryptText(key, value, VALUE_INFO)
5331
- });
5332
- }
5333
- this.transaction(() => {
5334
- this.sql.exec(`DELETE FROM app_secrets_store`);
5335
- for (const write of writes) {
5336
- this.sql.exec(
5337
- `INSERT INTO app_secrets_store
5338
- (name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
5339
- VALUES (?, ?, ?, 1, ?, ?, ?)`,
5340
- write.nameHash,
5341
- write.nameEncrypted,
5342
- write.valueEncrypted,
5343
- actor,
5344
- now,
5345
- now
5346
- );
5347
- }
5348
- this.bumpStoreVersion(now);
5349
- });
5350
- return { version: this.storeVersion(), secrets: await this.list() };
5351
- }
5352
- transaction(run) {
5353
- this.state.storage.transactionSync(run);
5354
- }
5355
- migrateLegacyProductionSecrets() {
5356
- const legacyExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'app_secrets'`).toArray().length > 0;
5357
- if (!legacyExists) return;
5358
- const currentCount = this.sql.exec(`SELECT COUNT(*) AS count FROM app_secrets_store`).toArray()[0];
5359
- if ((currentCount?.count ?? 0) > 0) return;
5360
- this.sql.exec(`
5361
- INSERT OR IGNORE INTO app_secrets_store
5362
- (name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
5363
- SELECT name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at
5364
- FROM app_secrets
5365
- WHERE env = 'production'
5366
- `);
5367
- const legacyVersionExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'app_secret_env_versions'`).toArray().length > 0;
5368
- const legacyVersion = legacyVersionExists ? this.sql.exec(`SELECT version, updated_at FROM app_secret_env_versions WHERE env = 'production'`).toArray()[0] : void 0;
5369
- if (legacyVersion) {
5370
- this.sql.exec(
5371
- `INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
5372
- STORE_ID,
5373
- legacyVersion.version,
5374
- legacyVersion.updated_at
5375
- );
5376
- }
5377
- const migratedCount = this.sql.exec(`SELECT COUNT(*) AS count FROM app_secrets_store`).toArray()[0];
5378
- if (!legacyVersion && (migratedCount?.count ?? 0) > 0) {
5379
- this.sql.exec(
5380
- `INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
5381
- STORE_ID,
5382
- 1,
5383
- (/* @__PURE__ */ new Date()).toISOString()
5384
- );
5385
- }
5386
- }
5387
- ensureInitialized() {
5388
- if (this.initialized) return;
5389
- this.initialized = true;
5390
- this.sql.exec(`
5391
- CREATE TABLE IF NOT EXISTS app_secrets_store (
5392
- name_hash TEXT NOT NULL,
5393
- name_encrypted TEXT NOT NULL,
5394
- value_encrypted TEXT NOT NULL,
5395
- version INTEGER NOT NULL,
5396
- updated_by TEXT,
5397
- created_at TEXT NOT NULL,
5398
- updated_at TEXT NOT NULL,
5399
- PRIMARY KEY (name_hash)
5400
- )
5401
- `);
5402
- this.sql.exec(`
5403
- CREATE TABLE IF NOT EXISTS app_secret_store_versions (
5404
- id TEXT PRIMARY KEY,
5405
- version INTEGER NOT NULL,
5406
- updated_at TEXT NOT NULL
5407
- )
5408
- `);
5409
- this.migrateLegacyProductionSecrets();
5410
- }
5411
- requireKey() {
5412
- if (!this.env.APP_SECRETS_KEY) {
5413
- throw new Error("APP_SECRETS_KEY binding is missing");
5414
- }
5415
- return this.env.APP_SECRETS_KEY;
5416
- }
5417
- storeVersion() {
5418
- const row = this.sql.exec(
5419
- `SELECT version FROM app_secret_store_versions WHERE id = ?`,
5420
- STORE_ID
5421
- ).toArray()[0];
5422
- return row?.version ?? 0;
5423
- }
5424
- bumpStoreVersion(now) {
5425
- const next = this.storeVersion() + 1;
5426
- this.sql.exec(
5427
- `INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
5428
- STORE_ID,
5429
- next,
5430
- now
5431
- );
5432
- }
5433
- };
5434
- function validateSecretName(name) {
5435
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
5436
- throw new Error(`Invalid secret name "${name}"`);
5437
- }
5438
- }
5439
- function json(body, status = 200) {
5440
- return new Response(JSON.stringify(body), {
5441
- status,
5442
- headers: { "Content-Type": "application/json" }
5443
- });
5444
- }
5445
- async function encryptText(root, plaintext, info) {
5446
- const salt = crypto.getRandomValues(new Uint8Array(16));
5447
- const iv = crypto.getRandomValues(new Uint8Array(12));
5448
- const key = await aesKey(root, salt, info);
5449
- const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, new TextEncoder().encode(plaintext));
5450
- return [VERSION, b64(salt), b64(iv), b64(new Uint8Array(ciphertext))].join(":");
5451
- }
5452
- async function decryptText(root, encoded, info) {
5453
- const [version, saltB64, ivB64, bodyB64] = encoded.split(":");
5454
- if (version !== VERSION || !saltB64 || !ivB64 || !bodyB64) {
5455
- throw new Error("Corrupt app secret ciphertext");
5456
- }
5457
- const key = await aesKey(root, unb64(saltB64), info);
5458
- const plaintext = await crypto.subtle.decrypt(
5459
- { name: "AES-GCM", iv: bytes(unb64(ivB64)) },
5460
- key,
5461
- bytes(unb64(bodyB64))
5462
- );
5463
- return new TextDecoder().decode(plaintext);
5464
- }
5465
- async function hashName(root, name) {
5466
- const key = await hmacKey(root, HMAC_INFO);
5467
- const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`production:${name}`));
5468
- return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
5469
- }
5470
- async function aesKey(root, salt, info) {
5471
- const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(root), "HKDF", false, ["deriveKey"]);
5472
- return crypto.subtle.deriveKey(
5473
- { name: "HKDF", hash: "SHA-256", salt: bytes(salt), info: new TextEncoder().encode(info) },
5474
- base,
5475
- { name: "AES-GCM", length: 256 },
5476
- false,
5477
- ["encrypt", "decrypt"]
5478
- );
5479
- }
5480
- async function hmacKey(root, info) {
5481
- const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(root), "HKDF", false, ["deriveKey"]);
5482
- return crypto.subtle.deriveKey(
5483
- { name: "HKDF", hash: "SHA-256", salt: bytes(new Uint8Array(16)), info: new TextEncoder().encode(info) },
5484
- base,
5485
- { name: "HMAC", hash: "SHA-256", length: 256 },
5486
- false,
5487
- ["sign"]
5488
- );
5489
- }
5490
- function b64(bytes2) {
5491
- let s = "";
5492
- for (const b of bytes2) s += String.fromCharCode(b);
5493
- return btoa(s);
5494
- }
5495
- function unb64(value) {
5496
- const s = atob(value);
5497
- const out = new Uint8Array(s.length);
5498
- for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i);
5499
- return out;
5500
- }
5501
- function bytes(input) {
5502
- return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
5503
- }
5504
-
5505
5215
  // src/server/rooms/job-room.ts
5506
5216
  var RUNNING_STALE_MS = 16 * 60 * 1e3;
5507
5217
  var DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1e3;
@@ -6119,7 +5829,6 @@ var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
6119
5829
  "AUTH_WORKER_URL",
6120
5830
  "APP_IDENTITY_TOKEN",
6121
5831
  "APP_OWNER_JWT",
6122
- "APP_SECRETS_KEY",
6123
5832
  "USAGE_EVENTS"
6124
5833
  ]);
6125
5834
  function validateBindingManifest(manifest) {
@@ -6956,8 +6665,8 @@ import { importSPKI, jwtVerify } from "jose";
6956
6665
 
6957
6666
  // src/server/auth/utils.ts
6958
6667
  var BASE64_URL_REGEX = /^[A-Za-z0-9-_]+$/;
6959
- function bufferToHex(bytes2) {
6960
- const view = bytes2 instanceof Uint8Array ? bytes2 : new Uint8Array(bytes2);
6668
+ function bufferToHex(bytes) {
6669
+ const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
6961
6670
  let hex = "";
6962
6671
  for (const byte of view) {
6963
6672
  hex += byte.toString(16).padStart(2, "0");
@@ -6980,9 +6689,9 @@ function decodeJwtPayload(token) {
6980
6689
  if (!BASE64_URL_REGEX.test(payloadSegment)) return void 0;
6981
6690
  try {
6982
6691
  const normalized = payloadSegment.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payloadSegment.length / 4) * 4, "=");
6983
- const bytes2 = base64ToUint8Array(normalized);
6984
- const json2 = new TextDecoder().decode(bytes2);
6985
- const payload = JSON.parse(json2);
6692
+ const bytes = base64ToUint8Array(normalized);
6693
+ const json = new TextDecoder().decode(bytes);
6694
+ const payload = JSON.parse(json);
6986
6695
  return {
6987
6696
  iss: payload.iss ?? null,
6988
6697
  aud: payload.aud ?? null,
@@ -7206,176 +6915,6 @@ async function generateAppleClientSecret(apple) {
7206
6915
  return token;
7207
6916
  }
7208
6917
 
7209
- // src/server/secrets.ts
7210
- var DEFAULT_REFRESH_INTERVAL_MS = 24 * 60 * 60 * 1e3;
7211
- function createSecrets(env, options = {}) {
7212
- const refreshIntervalMs = options.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS;
7213
- const now = options.now ?? (() => Date.now());
7214
- const cache = /* @__PURE__ */ new Map();
7215
- let loadedAll = null;
7216
- let loadedVersion = 0;
7217
- let lastCheckedAt = 0;
7218
- function stub() {
7219
- if (!env.SECRETS_ROOMS) throw new Error("SECRETS_ROOMS binding is missing");
7220
- if (!env.APP_SECRETS_KEY) throw new Error("APP_SECRETS_KEY binding is missing");
7221
- const id = env.SECRETS_ROOMS.idFromName("app-secrets");
7222
- return env.SECRETS_ROOMS.get(id);
7223
- }
7224
- async function fetchJson(path) {
7225
- const res = await stub().fetch(`https://app-secrets/${path}`, {
7226
- headers: { "x-deepspace-internal": "app-secrets" }
7227
- });
7228
- const body = await res.json().catch(() => ({}));
7229
- if (!res.ok) throw new Error(body.error ?? `App secrets unavailable (${res.status})`);
7230
- return body;
7231
- }
7232
- async function pullAll() {
7233
- const body = await fetchJson("pull");
7234
- const version = typeof body.version === "number" ? body.version : 0;
7235
- cache.clear();
7236
- for (const [name, value] of Object.entries(body.secrets ?? {})) {
7237
- cache.set(name, value);
7238
- }
7239
- loadedVersion = version;
7240
- lastCheckedAt = now();
7241
- return version;
7242
- }
7243
- async function loadAll() {
7244
- await pullAll();
7245
- }
7246
- async function ensureLoaded() {
7247
- if (!loadedAll) {
7248
- loadedAll = loadAll().catch((err) => {
7249
- loadedAll = null;
7250
- throw err;
7251
- });
7252
- }
7253
- await loadedAll;
7254
- }
7255
- async function maybeCheckForUpdates() {
7256
- if (!loadedAll) return;
7257
- if (refreshIntervalMs < 0) return;
7258
- if (now() - lastCheckedAt < refreshIntervalMs) return;
7259
- try {
7260
- await client.checkForUpdates();
7261
- } catch {
7262
- }
7263
- }
7264
- function localDevValue(name) {
7265
- const value = env[name];
7266
- return env.APP_SECRETS_LOCAL_FALLBACK === "true" && typeof value === "string" ? value : void 0;
7267
- }
7268
- const client = {
7269
- async get(name) {
7270
- const local = localDevValue(name);
7271
- if (local !== void 0) return local;
7272
- if (!cache.has(name)) await ensureLoaded();
7273
- else await maybeCheckForUpdates();
7274
- return cache.get(name);
7275
- },
7276
- async require(name) {
7277
- const value = await this.get(name);
7278
- if (value === void 0) throw new Error(`Missing app secret "${name}"`);
7279
- return value;
7280
- },
7281
- async getMany(names) {
7282
- const out = {};
7283
- for (const name of names) out[name] = await this.get(name);
7284
- return out;
7285
- },
7286
- async preload(names) {
7287
- if (env.APP_SECRETS_LOCAL_FALLBACK === "true") {
7288
- const missing = [];
7289
- for (const name of names) {
7290
- const local = localDevValue(name);
7291
- if (local !== void 0) cache.set(name, local);
7292
- else missing.push(name);
7293
- }
7294
- if (missing.length === 0) {
7295
- await maybeCheckForUpdates();
7296
- return;
7297
- }
7298
- }
7299
- await ensureLoaded();
7300
- await maybeCheckForUpdates();
7301
- },
7302
- async refresh(refreshOptions = {}) {
7303
- if (refreshOptions.force || !loadedAll) {
7304
- const previous = loadedVersion;
7305
- const version = await pullAll();
7306
- loadedAll = Promise.resolve();
7307
- return { changed: version !== previous, version };
7308
- }
7309
- return this.checkForUpdates();
7310
- },
7311
- async checkForUpdates() {
7312
- await ensureLoaded();
7313
- const body = await fetchJson("version");
7314
- const version = typeof body.version === "number" ? body.version : 0;
7315
- lastCheckedAt = now();
7316
- if (version === loadedVersion) return { changed: false, version };
7317
- const pulledVersion = await pullAll();
7318
- return { changed: true, version: pulledVersion };
7319
- },
7320
- invalidate() {
7321
- cache.clear();
7322
- loadedAll = null;
7323
- loadedVersion = 0;
7324
- lastCheckedAt = 0;
7325
- },
7326
- async withFreshSecrets(operation, retryOptions = {}) {
7327
- let first;
7328
- try {
7329
- first = await operation({ secrets: client, attempt: 1, refreshed: false });
7330
- } catch (err) {
7331
- if (!shouldRefreshOnError(err)) throw err;
7332
- const refresh = await refreshAfterFailure(err);
7333
- if (!refresh.changed && !retryOptions.retryWhenUnchanged) throw err;
7334
- return operation({ secrets: client, attempt: 2, refreshed: refresh.changed });
7335
- }
7336
- if (first instanceof Response && shouldRefreshOnStatus(first.status)) {
7337
- const refresh = await refreshAfterFailure(first);
7338
- if (refresh.changed || retryOptions.retryWhenUnchanged) {
7339
- return operation({ secrets: client, attempt: 2, refreshed: refresh.changed });
7340
- }
7341
- }
7342
- return first;
7343
- }
7344
- };
7345
- async function refreshAfterFailure(original) {
7346
- try {
7347
- return await client.refresh({ force: true });
7348
- } catch (err) {
7349
- const originalMessage = describeFailure(original);
7350
- const refreshMessage = err instanceof Error ? err.message : String(err);
7351
- throw new Error(`Secret refresh failed after API failure (${originalMessage}): ${refreshMessage}`);
7352
- }
7353
- }
7354
- return client;
7355
- }
7356
- function shouldRefreshOnError(error) {
7357
- const status = statusFromError(error);
7358
- return status !== void 0 && shouldRefreshOnStatus(status);
7359
- }
7360
- function shouldRefreshOnStatus(status) {
7361
- return status === 401 || status === 403;
7362
- }
7363
- function statusFromError(error) {
7364
- if (!error || typeof error !== "object") return void 0;
7365
- const record = error;
7366
- if (typeof record.status === "number") return record.status;
7367
- if (typeof record.statusCode === "number") return record.statusCode;
7368
- if (typeof record.response?.status === "number") return record.response.status;
7369
- return void 0;
7370
- }
7371
- function describeFailure(value) {
7372
- if (value instanceof Response) return `HTTP ${value.status}`;
7373
- if (value instanceof Error) return value.message;
7374
- const status = statusFromError(value);
7375
- if (status) return `HTTP ${status}`;
7376
- return String(value);
7377
- }
7378
-
7379
6918
  // src/shared/subscription.ts
7380
6919
  var ENTITLED_STATUSES = /* @__PURE__ */ new Set([
7381
6920
  "active",
@@ -7557,7 +7096,6 @@ export {
7557
7096
  SYSTEM_COLLECTION_SCHEMAS,
7558
7097
  SYSTEM_MANAGED_COLUMNS,
7559
7098
  SchemaRegistry,
7560
- SecretsRoom,
7561
7099
  SubscriptionAuthError,
7562
7100
  SubscriptionRequiredError,
7563
7101
  USERS_COLUMNS,
@@ -7594,7 +7132,6 @@ export {
7594
7132
  createDeepSpaceAI,
7595
7133
  createDeepSpaceAuth,
7596
7134
  createScopedR2Handler,
7597
- createSecrets,
7598
7135
  dataToColumnValues,
7599
7136
  decodeJwtPayload,
7600
7137
  deleteChatCascade,