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/worker.js CHANGED
@@ -70,7 +70,7 @@ var BaseRoom = class {
70
70
  console.error(`[${this.constructor.name}] Message error:`, e);
71
71
  }
72
72
  }
73
- async webSocketClose(ws, code, reason) {
73
+ async webSocketClose(ws, _code, _reason) {
74
74
  const attachment = ws.deserializeAttachment();
75
75
  if (attachment) {
76
76
  try {
@@ -95,12 +95,12 @@ var BaseRoom = class {
95
95
  * Return an augmented attachment to serialize on the WebSocket,
96
96
  * or void to use the default attachment.
97
97
  */
98
- onConnect(ws, user) {
98
+ onConnect(_ws, _user) {
99
99
  }
100
100
  /**
101
101
  * Called when a WebSocket disconnects.
102
102
  */
103
- onDisconnect(ws, user) {
103
+ onDisconnect(_ws, _user) {
104
104
  }
105
105
  // ==========================================================================
106
106
  // Utilities
@@ -350,7 +350,7 @@ function coerceValue(value, storage, interpretation) {
350
350
  if (m) return parseFloat(m[1]) / 100;
351
351
  }
352
352
  if (interpretation.kind === "currency") {
353
- const cleaned2 = s.replace(/[^0-9.\-]/g, "");
353
+ const cleaned2 = s.replace(/[^0-9.-]/g, "");
354
354
  const n2 = parseFloat(cleaned2);
355
355
  if (!isNaN(n2)) return n2;
356
356
  return s;
@@ -540,9 +540,6 @@ var SchemaRegistry = class {
540
540
  has(name) {
541
541
  return this.trusted.has(name);
542
542
  }
543
- hasTrusted(name) {
544
- return this.trusted.has(name);
545
- }
546
543
  all() {
547
544
  return Array.from(this.trusted.values());
548
545
  }
@@ -1141,7 +1138,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
1141
1138
  const columns = getResolvedColumns(schema);
1142
1139
  const existing = getRecord(ctx.sql, collection, recordId, schema);
1143
1140
  const isUpdate = existing !== null;
1144
- let mergedData = isUpdate ? { ...existing.data, ...data } : { ...data };
1141
+ const mergedData = isUpdate ? { ...existing.data, ...data } : { ...data };
1145
1142
  for (const col of columns) {
1146
1143
  if (col.readonly) continue;
1147
1144
  if (!isUpdate) {
@@ -1196,7 +1193,10 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
1196
1193
  const permCtx = ctx.getPermissionContext();
1197
1194
  if (isUpdate) {
1198
1195
  if (!canUpdate(schema, userRole, { ...existing, recordId }, userId, permCtx)) {
1199
- return { success: false, error: `UPDATE DENIED: role=${userRole}, collection=${collection}` };
1196
+ return {
1197
+ success: false,
1198
+ error: `UPDATE DENIED: role=${userRole}, collection=${collection}`
1199
+ };
1200
1200
  }
1201
1201
  const fieldError = checkFieldPermissions(schema, userRole, finalData, existing.data);
1202
1202
  if (fieldError) {
@@ -1204,7 +1204,10 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
1204
1204
  }
1205
1205
  } else {
1206
1206
  if (!canCreate(schema, userRole)) {
1207
- return { success: false, error: `CREATE DENIED: role=${userRole}, collection=${collection}` };
1207
+ return {
1208
+ success: false,
1209
+ error: `CREATE DENIED: role=${userRole}, collection=${collection}`
1210
+ };
1208
1211
  }
1209
1212
  }
1210
1213
  }
@@ -1226,7 +1229,10 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
1226
1229
  );
1227
1230
  if (existing2.toArray().length > 0) {
1228
1231
  const fields = schema.uniqueOn.map((f) => `${f}=${finalData[f] ?? "null"}`).join(", ");
1229
- return { success: false, error: `Duplicate: a record with ${fields} already exists in ${collection}` };
1232
+ return {
1233
+ success: false,
1234
+ error: `Duplicate: a record with ${fields} already exists in ${collection}`
1235
+ };
1230
1236
  }
1231
1237
  }
1232
1238
  if (isUpdate) {
@@ -1240,13 +1246,16 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
1240
1246
  ctx.sql.exec(`UPDATE "${tbl}" SET ${setClauses.join(", ")} WHERE _row_id = ?`, ...params);
1241
1247
  } else {
1242
1248
  const colIds = Object.keys(colValues);
1243
- const allCols = ["_row_id", "_created_by", "_created_at", "_updated_at", ...colIds.map((c) => `"${c}"`)];
1249
+ const allCols = [
1250
+ "_row_id",
1251
+ "_created_by",
1252
+ "_created_at",
1253
+ "_updated_at",
1254
+ ...colIds.map((c) => `"${c}"`)
1255
+ ];
1244
1256
  const placeholders = allCols.map(() => "?").join(", ");
1245
1257
  const params = [recordId, userId, now, now, ...colIds.map((c) => colValues[c])];
1246
- ctx.sql.exec(
1247
- `INSERT INTO "${tbl}" (${allCols.join(", ")}) VALUES (${placeholders})`,
1248
- ...params
1249
- );
1258
+ ctx.sql.exec(`INSERT INTO "${tbl}" (${allCols.join(", ")}) VALUES (${placeholders})`, ...params);
1250
1259
  }
1251
1260
  const saved = getRecord(ctx.sql, collection, recordId, schema);
1252
1261
  const record = {
@@ -2653,9 +2662,21 @@ var RecordRoom = class extends BaseRoom {
2653
2662
  ownerUserId;
2654
2663
  /** True until the first fetch() completes — detects hibernation wake-up */
2655
2664
  freshConstruct = true;
2665
+ /**
2666
+ * Per-connection `[DO Perf]` timing logs are noisy on every hot path, so
2667
+ * they're gated behind a `DEEPSPACE_DO_PERF` env binding (set it to any
2668
+ * truthy value on the worker to opt in). Off by default.
2669
+ */
2670
+ get perfLogEnabled() {
2671
+ return !!this.env?.DEEPSPACE_DO_PERF;
2672
+ }
2656
2673
  constructor(state, env, schemas = [], config = {}) {
2657
2674
  super(state, env);
2658
- this.schemaRegistry = new SchemaRegistry([...SYSTEM_COLLECTION_SCHEMAS, BASE_USERS_SCHEMA, ...schemas]);
2675
+ this.schemaRegistry = new SchemaRegistry([
2676
+ ...SYSTEM_COLLECTION_SCHEMAS,
2677
+ BASE_USERS_SCHEMA,
2678
+ ...schemas
2679
+ ]);
2659
2680
  this.ownerUserId = config.ownerUserId ?? null;
2660
2681
  const wsCount = this.state.getWebSockets().length;
2661
2682
  if (wsCount > 0) {
@@ -2739,10 +2760,7 @@ var RecordRoom = class extends BaseRoom {
2739
2760
  const now = (/* @__PURE__ */ new Date()).toISOString();
2740
2761
  for (const row of oldUsers) {
2741
2762
  const r = row;
2742
- const existing = this.sql.exec(
2743
- `SELECT 1 FROM c_users WHERE _row_id = ?`,
2744
- r.user_id
2745
- ).toArray();
2763
+ const existing = this.sql.exec(`SELECT 1 FROM c_users WHERE _row_id = ?`, r.user_id).toArray();
2746
2764
  if (existing.length > 0) continue;
2747
2765
  this.sql.exec(
2748
2766
  `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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -2776,7 +2794,6 @@ var RecordRoom = class extends BaseRoom {
2776
2794
  }
2777
2795
  let migrated = 0;
2778
2796
  let skipped = 0;
2779
- const now = (/* @__PURE__ */ new Date()).toISOString();
2780
2797
  for (const row of rows) {
2781
2798
  const r = row;
2782
2799
  const schema = this.schemaRegistry.get(r.collection);
@@ -2785,17 +2802,11 @@ var RecordRoom = class extends BaseRoom {
2785
2802
  continue;
2786
2803
  }
2787
2804
  const tbl = collectionTableName(r.collection);
2788
- const tblExists = this.sql.exec(
2789
- `SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
2790
- tbl
2791
- ).toArray().length > 0;
2805
+ const tblExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`, tbl).toArray().length > 0;
2792
2806
  if (!tblExists) {
2793
2807
  this.ensureCollectionTable(schema);
2794
2808
  }
2795
- const existing = this.sql.exec(
2796
- `SELECT 1 FROM "${tbl}" WHERE _row_id = ?`,
2797
- r.record_id
2798
- ).toArray();
2809
+ const existing = this.sql.exec(`SELECT 1 FROM "${tbl}" WHERE _row_id = ?`, r.record_id).toArray();
2799
2810
  if (existing.length > 0) {
2800
2811
  skipped++;
2801
2812
  continue;
@@ -2810,9 +2821,21 @@ var RecordRoom = class extends BaseRoom {
2810
2821
  const columns = schema.columns.map(resolveColumn);
2811
2822
  const colValues = dataToColumnValues(data, columns);
2812
2823
  const colIds = Object.keys(colValues);
2813
- const allCols = ["_row_id", "_created_by", "_created_at", "_updated_at", ...colIds.map((c) => `"${c}"`)];
2824
+ const allCols = [
2825
+ "_row_id",
2826
+ "_created_by",
2827
+ "_created_at",
2828
+ "_updated_at",
2829
+ ...colIds.map((c) => `"${c}"`)
2830
+ ];
2814
2831
  const placeholders = allCols.map(() => "?").join(", ");
2815
- const params = [r.record_id, r.created_by, r.created_at, r.updated_at, ...colIds.map((c) => colValues[c])];
2832
+ const params = [
2833
+ r.record_id,
2834
+ r.created_by,
2835
+ r.created_at,
2836
+ r.updated_at,
2837
+ ...colIds.map((c) => colValues[c])
2838
+ ];
2816
2839
  this.sql.exec(
2817
2840
  `INSERT INTO "${tbl}" (${allCols.join(", ")}) VALUES (${placeholders})`,
2818
2841
  ...params
@@ -2820,7 +2843,9 @@ var RecordRoom = class extends BaseRoom {
2820
2843
  migrated++;
2821
2844
  }
2822
2845
  if (migrated > 0 || skipped > 0) {
2823
- console.log(`[RecordRoom] Migrated ${migrated} records from document-mode, ${skipped} skipped`);
2846
+ console.log(
2847
+ `[RecordRoom] Migrated ${migrated} records from document-mode, ${skipped} skipped`
2848
+ );
2824
2849
  }
2825
2850
  this.sql.exec(`DROP TABLE IF EXISTS records`);
2826
2851
  } catch (error) {
@@ -2843,7 +2868,12 @@ var RecordRoom = class extends BaseRoom {
2843
2868
  // ============================================================================
2844
2869
  async onConnect(ws, user) {
2845
2870
  await this.ensureInitialized();
2846
- const timing = this._fetchTiming ?? { fetchStart: Date.now(), isColdStart: false, needsInit: false, initMs: 0 };
2871
+ const timing = this._fetchTiming ?? {
2872
+ fetchStart: Date.now(),
2873
+ isColdStart: false,
2874
+ needsInit: false,
2875
+ initMs: 0
2876
+ };
2847
2877
  const userId = user.userId;
2848
2878
  const userName = user.userName;
2849
2879
  const userEmail = user.userEmail;
@@ -2854,7 +2884,16 @@ var RecordRoom = class extends BaseRoom {
2854
2884
  const isOwner2 = this.ownerUserId != null && userId === this.ownerUserId;
2855
2885
  const regStart = Date.now();
2856
2886
  const defaultRole = this.schemaRegistry.get("users")?.defaultRole ?? "member";
2857
- const registeredUser = await registerUser(this.sql, userId, userName, userEmail, userImageUrl, isOwner2, defaultRole, this.schemaRegistry);
2887
+ const registeredUser = await registerUser(
2888
+ this.sql,
2889
+ userId,
2890
+ userName,
2891
+ userEmail,
2892
+ userImageUrl,
2893
+ isOwner2,
2894
+ defaultRole,
2895
+ this.schemaRegistry
2896
+ );
2858
2897
  const regMs = Date.now() - regStart;
2859
2898
  attachment = {
2860
2899
  userId: registeredUser.id,
@@ -2868,7 +2907,10 @@ var RecordRoom = class extends BaseRoom {
2868
2907
  };
2869
2908
  this.send(ws, { type: MSG.USER_INFO, payload: registeredUser });
2870
2909
  const totalMs = Date.now() - timing.fetchStart;
2871
- console.log(`[DO Perf] cold=${timing.isColdStart} | init: ${timing.initMs}ms reg: ${regMs}ms | total: ${totalMs}ms`);
2910
+ if (this.perfLogEnabled)
2911
+ console.log(
2912
+ `[DO Perf] cold=${timing.isColdStart} | init: ${timing.initMs}ms reg: ${regMs}ms | total: ${totalMs}ms`
2913
+ );
2872
2914
  } else {
2873
2915
  attachment = {
2874
2916
  userId,
@@ -2879,63 +2921,37 @@ var RecordRoom = class extends BaseRoom {
2879
2921
  yjsSubscriptions: [],
2880
2922
  yjsClientId: this.nextYjsClientId++
2881
2923
  };
2882
- this.send(ws, { type: MSG.USER_INFO, payload: {
2883
- id: userId,
2884
- name: "Anonymous",
2885
- email: "",
2886
- role: ROLE_ANONYMOUS,
2887
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2888
- lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
2889
- } });
2924
+ this.send(ws, {
2925
+ type: MSG.USER_INFO,
2926
+ payload: {
2927
+ id: userId,
2928
+ name: "Anonymous",
2929
+ email: "",
2930
+ role: ROLE_ANONYMOUS,
2931
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2932
+ lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
2933
+ }
2934
+ });
2890
2935
  const totalMs = Date.now() - timing.fetchStart;
2891
- console.log(`[DO Perf] cold=${timing.isColdStart} anon | total: ${totalMs}ms`);
2936
+ if (this.perfLogEnabled)
2937
+ console.log(`[DO Perf] cold=${timing.isColdStart} anon | total: ${totalMs}ms`);
2892
2938
  }
2893
2939
  this._fetchTiming = null;
2894
2940
  return attachment;
2895
2941
  }
2896
- async onMessage(ws, user, msg) {
2897
- await this.ensureInitialized();
2898
- const attachment = ws.deserializeAttachment();
2899
- if (!attachment) {
2900
- this.send(ws, { type: MSG.ERROR, payload: { error: "Connection not found" } });
2901
- return;
2902
- }
2903
- try {
2904
- await this.handleRecordMessage(ws, attachment, msg);
2905
- } catch (e) {
2906
- const errMsg = e instanceof Error ? e.message : String(e);
2907
- console.error(`[RecordRoom] Message handler error: ${errMsg}`);
2908
- this.send(ws, { type: MSG.ERROR, payload: { error: `Invalid message: ${errMsg}` } });
2909
- }
2910
- }
2911
- async onBinaryMessage(ws, user, data) {
2912
- await this.ensureInitialized();
2913
- const attachment = ws.deserializeAttachment();
2914
- if (!attachment) return;
2915
- try {
2916
- await handleYjsBinaryMessage(
2917
- this.createYjsContext(),
2918
- ws,
2919
- attachment,
2920
- new Uint8Array(data)
2921
- );
2922
- } catch (e) {
2923
- console.error("[RecordRoom] Yjs binary message error:", e);
2924
- }
2925
- }
2926
- onDisconnect(ws, user) {
2927
- console.log(`[RecordRoom] webSocketClose`);
2928
- try {
2929
- const attachment = ws.deserializeAttachment();
2930
- if (attachment) {
2931
- broadcastAwarenessRemoval(this.createYjsContext(), attachment);
2932
- }
2933
- } catch {
2934
- }
2942
+ /**
2943
+ * Required to satisfy BaseRoom's abstract contract, but never invoked:
2944
+ * RecordRoom overrides `webSocketMessage`/`webSocketClose` directly (below),
2945
+ * so the BaseRoom dispatch path that would call this never runs. All real
2946
+ * message handling lives in `handleRecordMessage`.
2947
+ */
2948
+ onMessage() {
2949
+ throw new Error("RecordRoom.onMessage is unreachable; use webSocketMessage");
2935
2950
  }
2936
2951
  // ============================================================================
2937
- // Hibernation API (delegates to BaseRoom via super, but we need
2938
- // to ensure webSocketMessage gets the ensureInitialized treatment)
2952
+ // Hibernation API — RecordRoom overrides these directly (rather than using
2953
+ // BaseRoom's onMessage/onBinaryMessage/onDisconnect hooks) so it can run
2954
+ // ensureInitialized() before every message after a hibernation wake-up.
2939
2955
  // ============================================================================
2940
2956
  async webSocketMessage(ws, message) {
2941
2957
  await this.ensureInitialized();
@@ -3006,7 +3022,12 @@ var RecordRoom = class extends BaseRoom {
3006
3022
  handleUserList(userCtx, ws, attachment);
3007
3023
  break;
3008
3024
  case MSG.USER_UPDATE:
3009
- handleUserUpdate(recordCtx, ws, attachment, payload);
3025
+ handleUserUpdate(
3026
+ recordCtx,
3027
+ ws,
3028
+ attachment,
3029
+ payload
3030
+ );
3010
3031
  break;
3011
3032
  case MSG.SET_ROLE:
3012
3033
  await handleSetRole(userCtx, ws, attachment, payload);
@@ -3172,19 +3193,19 @@ var YjsRoom = class extends BaseRoom {
3172
3193
  this.sendAwarenessSnapshot(ws);
3173
3194
  return attachment;
3174
3195
  }
3175
- onMessage(ws, user, message) {
3196
+ onMessage(_ws, _user, _message) {
3176
3197
  }
3177
3198
  onBinaryMessage(ws, user, data) {
3178
- const bytes2 = new Uint8Array(data);
3179
- const decoder = createDecoder(bytes2);
3199
+ const bytes = new Uint8Array(data);
3200
+ const decoder = createDecoder(bytes);
3180
3201
  const messageType = readVarUint(decoder);
3181
3202
  if (messageType === MSG_SYNC) {
3182
- this.handleSync(ws, decoder, bytes2);
3203
+ this.handleSync(ws, decoder, bytes);
3183
3204
  } else if (messageType === MSG_AWARENESS) {
3184
- this.handleAwareness(ws, decoder, bytes2);
3205
+ this.handleAwareness(ws, decoder, bytes);
3185
3206
  }
3186
3207
  }
3187
- onDisconnect(ws, user) {
3208
+ onDisconnect(ws, _user) {
3188
3209
  const attachment = ws.deserializeAttachment();
3189
3210
  if (attachment?.awarenessClientId == null) return;
3190
3211
  const clientId = attachment.awarenessClientId;
@@ -3545,16 +3566,16 @@ var GameRoom = class extends BaseRoom {
3545
3566
  return this.currentTick;
3546
3567
  }
3547
3568
  /** Called when a player connects */
3548
- onPlayerJoin(player) {
3569
+ onPlayerJoin(_player) {
3549
3570
  }
3550
3571
  /** Called when a player disconnects */
3551
- onPlayerLeave(player) {
3572
+ onPlayerLeave(_player) {
3552
3573
  }
3553
3574
  /** Called when the game starts */
3554
3575
  onGameStart() {
3555
3576
  }
3556
3577
  /** Called when the game ends */
3557
- onGameEnd(finalState) {
3578
+ onGameEnd(_finalState) {
3558
3579
  }
3559
3580
  /**
3560
3581
  * Called once when the DO first hydrates persisted state from storage.
@@ -4386,319 +4407,6 @@ function cronMatches(parsed, timezone, date) {
4386
4407
  return parsed.minute.has(minute) && parsed.hour.has(hour) && parsed.dayOfMonth.has(dayOfMonth) && parsed.month.has(month) && parsed.dayOfWeek.has(dayOfWeek);
4387
4408
  }
4388
4409
 
4389
- // src/server/rooms/secrets-room.ts
4390
- var INTERNAL_HEADER = "x-deepspace-internal";
4391
- var INTERNAL_VALUE = "app-secrets";
4392
- var VERSION = "app-secret-v1";
4393
- var NAME_INFO = "deepspace app secret name v1";
4394
- var VALUE_INFO = "deepspace app secret value v1";
4395
- var HMAC_INFO = "deepspace app secret lookup v1";
4396
- var STORE_ID = "default";
4397
- var SecretsRoom = class extends BaseRoom {
4398
- initialized = false;
4399
- onMessage(_ws, _user, _message) {
4400
- }
4401
- async fetch(request) {
4402
- this.ensureInitialized();
4403
- return super.fetch(request);
4404
- }
4405
- async onRequest(request) {
4406
- if (request.headers.get(INTERNAL_HEADER) !== INTERNAL_VALUE) {
4407
- return json({ error: "Not found" }, 404);
4408
- }
4409
- const url = new URL(request.url);
4410
- try {
4411
- if (request.method === "GET" && url.pathname.endsWith("/list")) {
4412
- return json({ version: this.storeVersion(), secrets: await this.list() });
4413
- }
4414
- if (request.method === "GET" && url.pathname.endsWith("/pull")) {
4415
- return json({ version: this.storeVersion(), secrets: await this.pull() });
4416
- }
4417
- if (request.method === "GET" && url.pathname.endsWith("/version")) {
4418
- return json({ version: this.storeVersion() });
4419
- }
4420
- if (request.method === "PATCH" && url.pathname.endsWith("/patch")) {
4421
- const body = await request.json();
4422
- const result = await this.patch(body);
4423
- return json(result);
4424
- }
4425
- if (request.method === "PUT" && url.pathname.endsWith("/replace")) {
4426
- const body = await request.json();
4427
- const result = await this.replace(body);
4428
- return json(result);
4429
- }
4430
- } catch (err) {
4431
- const message = err instanceof Error ? err.message : String(err);
4432
- const status = message.includes("conflict") ? 409 : 500;
4433
- return json({ error: message }, status);
4434
- }
4435
- return json({ error: "Not found" }, 404);
4436
- }
4437
- async list() {
4438
- const rows = this.sql.exec(
4439
- `SELECT name_encrypted, version, updated_by, created_at, updated_at
4440
- FROM app_secrets_store ORDER BY updated_at DESC`
4441
- ).toArray();
4442
- const out = [];
4443
- for (const row of rows) {
4444
- out.push({
4445
- name: await decryptText(this.requireKey(), row.name_encrypted, NAME_INFO),
4446
- version: row.version,
4447
- createdAt: row.created_at,
4448
- updatedAt: row.updated_at,
4449
- updatedBy: row.updated_by
4450
- });
4451
- }
4452
- return out.sort((a, b) => a.name.localeCompare(b.name));
4453
- }
4454
- async pull() {
4455
- const rows = this.sql.exec(
4456
- `SELECT name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at
4457
- FROM app_secrets_store`
4458
- ).toArray();
4459
- const out = {};
4460
- for (const row of rows) {
4461
- const name = await decryptText(this.requireKey(), row.name_encrypted, NAME_INFO);
4462
- out[name] = await decryptText(this.requireKey(), row.value_encrypted, VALUE_INFO);
4463
- }
4464
- return out;
4465
- }
4466
- async patch(body) {
4467
- const now = (/* @__PURE__ */ new Date()).toISOString();
4468
- const actor = body.updatedBy ?? null;
4469
- const key = this.requireKey();
4470
- const setEntries = Object.entries(body.set ?? {});
4471
- const deleteNames = body.delete ?? [];
4472
- for (const name of [...setEntries.map(([name2]) => name2), ...deleteNames]) {
4473
- validateSecretName(name);
4474
- }
4475
- const writes = [];
4476
- for (const [name, value] of setEntries) {
4477
- const nameHash = await hashName(key, name);
4478
- const existing = this.sql.exec(
4479
- `SELECT version, created_at FROM app_secrets_store WHERE name_hash = ?`,
4480
- nameHash
4481
- ).toArray()[0];
4482
- writes.push({
4483
- nameHash,
4484
- nameEncrypted: await encryptText(key, name, NAME_INFO),
4485
- valueEncrypted: await encryptText(key, value, VALUE_INFO),
4486
- version: (existing?.version ?? 0) + 1,
4487
- createdAt: existing?.created_at ?? now
4488
- });
4489
- }
4490
- const deletes = [];
4491
- for (const name of deleteNames) {
4492
- deletes.push(await hashName(key, name));
4493
- }
4494
- this.transaction(() => {
4495
- for (const write of writes) {
4496
- this.sql.exec(
4497
- `INSERT OR REPLACE INTO app_secrets_store
4498
- (name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
4499
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
4500
- write.nameHash,
4501
- write.nameEncrypted,
4502
- write.valueEncrypted,
4503
- write.version,
4504
- actor,
4505
- write.createdAt,
4506
- now
4507
- );
4508
- }
4509
- for (const nameHash of deletes) {
4510
- this.sql.exec(`DELETE FROM app_secrets_store WHERE name_hash = ?`, nameHash);
4511
- }
4512
- this.bumpStoreVersion(now);
4513
- });
4514
- return { version: this.storeVersion(), secrets: await this.list() };
4515
- }
4516
- async replace(body) {
4517
- const next = body.secrets ?? {};
4518
- for (const name of Object.keys(next)) validateSecretName(name);
4519
- const now = (/* @__PURE__ */ new Date()).toISOString();
4520
- const actor = body.updatedBy ?? null;
4521
- const key = this.requireKey();
4522
- const writes = [];
4523
- for (const [name, value] of Object.entries(next)) {
4524
- writes.push({
4525
- nameHash: await hashName(key, name),
4526
- nameEncrypted: await encryptText(key, name, NAME_INFO),
4527
- valueEncrypted: await encryptText(key, value, VALUE_INFO)
4528
- });
4529
- }
4530
- this.transaction(() => {
4531
- this.sql.exec(`DELETE FROM app_secrets_store`);
4532
- for (const write of writes) {
4533
- this.sql.exec(
4534
- `INSERT INTO app_secrets_store
4535
- (name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
4536
- VALUES (?, ?, ?, 1, ?, ?, ?)`,
4537
- write.nameHash,
4538
- write.nameEncrypted,
4539
- write.valueEncrypted,
4540
- actor,
4541
- now,
4542
- now
4543
- );
4544
- }
4545
- this.bumpStoreVersion(now);
4546
- });
4547
- return { version: this.storeVersion(), secrets: await this.list() };
4548
- }
4549
- transaction(run) {
4550
- this.state.storage.transactionSync(run);
4551
- }
4552
- migrateLegacyProductionSecrets() {
4553
- const legacyExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'app_secrets'`).toArray().length > 0;
4554
- if (!legacyExists) return;
4555
- const currentCount = this.sql.exec(`SELECT COUNT(*) AS count FROM app_secrets_store`).toArray()[0];
4556
- if ((currentCount?.count ?? 0) > 0) return;
4557
- this.sql.exec(`
4558
- INSERT OR IGNORE INTO app_secrets_store
4559
- (name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at)
4560
- SELECT name_hash, name_encrypted, value_encrypted, version, updated_by, created_at, updated_at
4561
- FROM app_secrets
4562
- WHERE env = 'production'
4563
- `);
4564
- const legacyVersionExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'app_secret_env_versions'`).toArray().length > 0;
4565
- const legacyVersion = legacyVersionExists ? this.sql.exec(`SELECT version, updated_at FROM app_secret_env_versions WHERE env = 'production'`).toArray()[0] : void 0;
4566
- if (legacyVersion) {
4567
- this.sql.exec(
4568
- `INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
4569
- STORE_ID,
4570
- legacyVersion.version,
4571
- legacyVersion.updated_at
4572
- );
4573
- }
4574
- const migratedCount = this.sql.exec(`SELECT COUNT(*) AS count FROM app_secrets_store`).toArray()[0];
4575
- if (!legacyVersion && (migratedCount?.count ?? 0) > 0) {
4576
- this.sql.exec(
4577
- `INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
4578
- STORE_ID,
4579
- 1,
4580
- (/* @__PURE__ */ new Date()).toISOString()
4581
- );
4582
- }
4583
- }
4584
- ensureInitialized() {
4585
- if (this.initialized) return;
4586
- this.initialized = true;
4587
- this.sql.exec(`
4588
- CREATE TABLE IF NOT EXISTS app_secrets_store (
4589
- name_hash TEXT NOT NULL,
4590
- name_encrypted TEXT NOT NULL,
4591
- value_encrypted TEXT NOT NULL,
4592
- version INTEGER NOT NULL,
4593
- updated_by TEXT,
4594
- created_at TEXT NOT NULL,
4595
- updated_at TEXT NOT NULL,
4596
- PRIMARY KEY (name_hash)
4597
- )
4598
- `);
4599
- this.sql.exec(`
4600
- CREATE TABLE IF NOT EXISTS app_secret_store_versions (
4601
- id TEXT PRIMARY KEY,
4602
- version INTEGER NOT NULL,
4603
- updated_at TEXT NOT NULL
4604
- )
4605
- `);
4606
- this.migrateLegacyProductionSecrets();
4607
- }
4608
- requireKey() {
4609
- if (!this.env.APP_SECRETS_KEY) {
4610
- throw new Error("APP_SECRETS_KEY binding is missing");
4611
- }
4612
- return this.env.APP_SECRETS_KEY;
4613
- }
4614
- storeVersion() {
4615
- const row = this.sql.exec(
4616
- `SELECT version FROM app_secret_store_versions WHERE id = ?`,
4617
- STORE_ID
4618
- ).toArray()[0];
4619
- return row?.version ?? 0;
4620
- }
4621
- bumpStoreVersion(now) {
4622
- const next = this.storeVersion() + 1;
4623
- this.sql.exec(
4624
- `INSERT OR REPLACE INTO app_secret_store_versions (id, version, updated_at) VALUES (?, ?, ?)`,
4625
- STORE_ID,
4626
- next,
4627
- now
4628
- );
4629
- }
4630
- };
4631
- function validateSecretName(name) {
4632
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
4633
- throw new Error(`Invalid secret name "${name}"`);
4634
- }
4635
- }
4636
- function json(body, status = 200) {
4637
- return new Response(JSON.stringify(body), {
4638
- status,
4639
- headers: { "Content-Type": "application/json" }
4640
- });
4641
- }
4642
- async function encryptText(root, plaintext, info) {
4643
- const salt = crypto.getRandomValues(new Uint8Array(16));
4644
- const iv = crypto.getRandomValues(new Uint8Array(12));
4645
- const key = await aesKey(root, salt, info);
4646
- const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, new TextEncoder().encode(plaintext));
4647
- return [VERSION, b64(salt), b64(iv), b64(new Uint8Array(ciphertext))].join(":");
4648
- }
4649
- async function decryptText(root, encoded, info) {
4650
- const [version, saltB64, ivB64, bodyB64] = encoded.split(":");
4651
- if (version !== VERSION || !saltB64 || !ivB64 || !bodyB64) {
4652
- throw new Error("Corrupt app secret ciphertext");
4653
- }
4654
- const key = await aesKey(root, unb64(saltB64), info);
4655
- const plaintext = await crypto.subtle.decrypt(
4656
- { name: "AES-GCM", iv: bytes(unb64(ivB64)) },
4657
- key,
4658
- bytes(unb64(bodyB64))
4659
- );
4660
- return new TextDecoder().decode(plaintext);
4661
- }
4662
- async function hashName(root, name) {
4663
- const key = await hmacKey(root, HMAC_INFO);
4664
- const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`production:${name}`));
4665
- return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
4666
- }
4667
- async function aesKey(root, salt, info) {
4668
- const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(root), "HKDF", false, ["deriveKey"]);
4669
- return crypto.subtle.deriveKey(
4670
- { name: "HKDF", hash: "SHA-256", salt: bytes(salt), info: new TextEncoder().encode(info) },
4671
- base,
4672
- { name: "AES-GCM", length: 256 },
4673
- false,
4674
- ["encrypt", "decrypt"]
4675
- );
4676
- }
4677
- async function hmacKey(root, info) {
4678
- const base = await crypto.subtle.importKey("raw", new TextEncoder().encode(root), "HKDF", false, ["deriveKey"]);
4679
- return crypto.subtle.deriveKey(
4680
- { name: "HKDF", hash: "SHA-256", salt: bytes(new Uint8Array(16)), info: new TextEncoder().encode(info) },
4681
- base,
4682
- { name: "HMAC", hash: "SHA-256", length: 256 },
4683
- false,
4684
- ["sign"]
4685
- );
4686
- }
4687
- function b64(bytes2) {
4688
- let s = "";
4689
- for (const b of bytes2) s += String.fromCharCode(b);
4690
- return btoa(s);
4691
- }
4692
- function unb64(value) {
4693
- const s = atob(value);
4694
- const out = new Uint8Array(s.length);
4695
- for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i);
4696
- return out;
4697
- }
4698
- function bytes(input) {
4699
- return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
4700
- }
4701
-
4702
4410
  // src/server/rooms/job-room.ts
4703
4411
  var RUNNING_STALE_MS = 16 * 60 * 1e3;
4704
4412
  var DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1e3;
@@ -5316,7 +5024,6 @@ var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
5316
5024
  "AUTH_WORKER_URL",
5317
5025
  "APP_IDENTITY_TOKEN",
5318
5026
  "APP_OWNER_JWT",
5319
- "APP_SECRETS_KEY",
5320
5027
  "USAGE_EVENTS"
5321
5028
  ]);
5322
5029
  function validateBindingManifest(manifest) {
@@ -6453,7 +6160,8 @@ function buildCronContext(env, ownerUserId, roomId = "default") {
6453
6160
  if (opts?.where) params.where = opts.where;
6454
6161
  if (opts?.limit) params.limit = opts.limit;
6455
6162
  const data = await executeTool3("records.query", params);
6456
- return data.records || [];
6163
+ const records2 = data?.records;
6164
+ return records2 ?? [];
6457
6165
  },
6458
6166
  async create(collection, data) {
6459
6167
  return executeTool3("records.create", { collection, data });
@@ -6610,7 +6318,8 @@ async function composioTools(env, options) {
6610
6318
  if (!list.ok || !list.success) {
6611
6319
  throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`);
6612
6320
  }
6613
- const items = Array.isArray(list.data?.items) ? list.data.items : [];
6321
+ const listItems = list.data?.items;
6322
+ const items = Array.isArray(listItems) ? listItems : [];
6614
6323
  const tools = {};
6615
6324
  for (const item of items) {
6616
6325
  if (!item?.slug) continue;
@@ -7244,8 +6953,8 @@ import { importSPKI, jwtVerify } from "jose";
7244
6953
 
7245
6954
  // src/server/auth/utils.ts
7246
6955
  var BASE64_URL_REGEX = /^[A-Za-z0-9-_]+$/;
7247
- function bufferToHex(bytes2) {
7248
- const view = bytes2 instanceof Uint8Array ? bytes2 : new Uint8Array(bytes2);
6956
+ function bufferToHex(bytes) {
6957
+ const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
7249
6958
  let hex = "";
7250
6959
  for (const byte of view) {
7251
6960
  hex += byte.toString(16).padStart(2, "0");
@@ -7268,9 +6977,9 @@ function decodeJwtPayload(token) {
7268
6977
  if (!BASE64_URL_REGEX.test(payloadSegment)) return void 0;
7269
6978
  try {
7270
6979
  const normalized = payloadSegment.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(payloadSegment.length / 4) * 4, "=");
7271
- const bytes2 = base64ToUint8Array(normalized);
7272
- const json2 = new TextDecoder().decode(bytes2);
7273
- const payload = JSON.parse(json2);
6980
+ const bytes = base64ToUint8Array(normalized);
6981
+ const json = new TextDecoder().decode(bytes);
6982
+ const payload = JSON.parse(json);
7274
6983
  return {
7275
6984
  iss: payload.iss ?? null,
7276
6985
  aud: payload.aud ?? null,
@@ -7493,176 +7202,6 @@ async function generateAppleClientSecret(apple) {
7493
7202
  cachedAppleSecret = { keyId: apple.keyId, token, expiresAt };
7494
7203
  return token;
7495
7204
  }
7496
-
7497
- // src/server/secrets.ts
7498
- var DEFAULT_REFRESH_INTERVAL_MS = 24 * 60 * 60 * 1e3;
7499
- function createSecrets(env, options = {}) {
7500
- const refreshIntervalMs = options.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS;
7501
- const now = options.now ?? (() => Date.now());
7502
- const cache = /* @__PURE__ */ new Map();
7503
- let loadedAll = null;
7504
- let loadedVersion = 0;
7505
- let lastCheckedAt = 0;
7506
- function stub() {
7507
- if (!env.SECRETS_ROOMS) throw new Error("SECRETS_ROOMS binding is missing");
7508
- if (!env.APP_SECRETS_KEY) throw new Error("APP_SECRETS_KEY binding is missing");
7509
- const id = env.SECRETS_ROOMS.idFromName("app-secrets");
7510
- return env.SECRETS_ROOMS.get(id);
7511
- }
7512
- async function fetchJson(path) {
7513
- const res = await stub().fetch(`https://app-secrets/${path}`, {
7514
- headers: { "x-deepspace-internal": "app-secrets" }
7515
- });
7516
- const body = await res.json().catch(() => ({}));
7517
- if (!res.ok) throw new Error(body.error ?? `App secrets unavailable (${res.status})`);
7518
- return body;
7519
- }
7520
- async function pullAll() {
7521
- const body = await fetchJson("pull");
7522
- const version = typeof body.version === "number" ? body.version : 0;
7523
- cache.clear();
7524
- for (const [name, value] of Object.entries(body.secrets ?? {})) {
7525
- cache.set(name, value);
7526
- }
7527
- loadedVersion = version;
7528
- lastCheckedAt = now();
7529
- return version;
7530
- }
7531
- async function loadAll() {
7532
- await pullAll();
7533
- }
7534
- async function ensureLoaded() {
7535
- if (!loadedAll) {
7536
- loadedAll = loadAll().catch((err) => {
7537
- loadedAll = null;
7538
- throw err;
7539
- });
7540
- }
7541
- await loadedAll;
7542
- }
7543
- async function maybeCheckForUpdates() {
7544
- if (!loadedAll) return;
7545
- if (refreshIntervalMs < 0) return;
7546
- if (now() - lastCheckedAt < refreshIntervalMs) return;
7547
- try {
7548
- await client.checkForUpdates();
7549
- } catch {
7550
- }
7551
- }
7552
- function localDevValue(name) {
7553
- const value = env[name];
7554
- return env.APP_SECRETS_LOCAL_FALLBACK === "true" && typeof value === "string" ? value : void 0;
7555
- }
7556
- const client = {
7557
- async get(name) {
7558
- const local = localDevValue(name);
7559
- if (local !== void 0) return local;
7560
- if (!cache.has(name)) await ensureLoaded();
7561
- else await maybeCheckForUpdates();
7562
- return cache.get(name);
7563
- },
7564
- async require(name) {
7565
- const value = await this.get(name);
7566
- if (value === void 0) throw new Error(`Missing app secret "${name}"`);
7567
- return value;
7568
- },
7569
- async getMany(names) {
7570
- const out = {};
7571
- for (const name of names) out[name] = await this.get(name);
7572
- return out;
7573
- },
7574
- async preload(names) {
7575
- if (env.APP_SECRETS_LOCAL_FALLBACK === "true") {
7576
- const missing = [];
7577
- for (const name of names) {
7578
- const local = localDevValue(name);
7579
- if (local !== void 0) cache.set(name, local);
7580
- else missing.push(name);
7581
- }
7582
- if (missing.length === 0) {
7583
- await maybeCheckForUpdates();
7584
- return;
7585
- }
7586
- }
7587
- await ensureLoaded();
7588
- await maybeCheckForUpdates();
7589
- },
7590
- async refresh(refreshOptions = {}) {
7591
- if (refreshOptions.force || !loadedAll) {
7592
- const previous = loadedVersion;
7593
- const version = await pullAll();
7594
- loadedAll = Promise.resolve();
7595
- return { changed: version !== previous, version };
7596
- }
7597
- return this.checkForUpdates();
7598
- },
7599
- async checkForUpdates() {
7600
- await ensureLoaded();
7601
- const body = await fetchJson("version");
7602
- const version = typeof body.version === "number" ? body.version : 0;
7603
- lastCheckedAt = now();
7604
- if (version === loadedVersion) return { changed: false, version };
7605
- const pulledVersion = await pullAll();
7606
- return { changed: true, version: pulledVersion };
7607
- },
7608
- invalidate() {
7609
- cache.clear();
7610
- loadedAll = null;
7611
- loadedVersion = 0;
7612
- lastCheckedAt = 0;
7613
- },
7614
- async withFreshSecrets(operation, retryOptions = {}) {
7615
- let first;
7616
- try {
7617
- first = await operation({ secrets: client, attempt: 1, refreshed: false });
7618
- } catch (err) {
7619
- if (!shouldRefreshOnError(err)) throw err;
7620
- const refresh = await refreshAfterFailure(err);
7621
- if (!refresh.changed && !retryOptions.retryWhenUnchanged) throw err;
7622
- return operation({ secrets: client, attempt: 2, refreshed: refresh.changed });
7623
- }
7624
- if (first instanceof Response && shouldRefreshOnStatus(first.status)) {
7625
- const refresh = await refreshAfterFailure(first);
7626
- if (refresh.changed || retryOptions.retryWhenUnchanged) {
7627
- return operation({ secrets: client, attempt: 2, refreshed: refresh.changed });
7628
- }
7629
- }
7630
- return first;
7631
- }
7632
- };
7633
- async function refreshAfterFailure(original) {
7634
- try {
7635
- return await client.refresh({ force: true });
7636
- } catch (err) {
7637
- const originalMessage = describeFailure(original);
7638
- const refreshMessage = err instanceof Error ? err.message : String(err);
7639
- throw new Error(`Secret refresh failed after API failure (${originalMessage}): ${refreshMessage}`);
7640
- }
7641
- }
7642
- return client;
7643
- }
7644
- function shouldRefreshOnError(error) {
7645
- const status = statusFromError(error);
7646
- return status !== void 0 && shouldRefreshOnStatus(status);
7647
- }
7648
- function shouldRefreshOnStatus(status) {
7649
- return status === 401 || status === 403;
7650
- }
7651
- function statusFromError(error) {
7652
- if (!error || typeof error !== "object") return void 0;
7653
- const record = error;
7654
- if (typeof record.status === "number") return record.status;
7655
- if (typeof record.statusCode === "number") return record.statusCode;
7656
- if (typeof record.response?.status === "number") return record.response.status;
7657
- return void 0;
7658
- }
7659
- function describeFailure(value) {
7660
- if (value instanceof Response) return `HTTP ${value.status}`;
7661
- if (value instanceof Error) return value.message;
7662
- const status = statusFromError(value);
7663
- if (status) return `HTTP ${status}`;
7664
- return String(value);
7665
- }
7666
7205
  export {
7667
7206
  AI_CHATS_SCHEMA,
7668
7207
  AI_MESSAGES_SCHEMA,
@@ -7710,7 +7249,6 @@ export {
7710
7249
  SYSTEM_COLLECTIONS,
7711
7250
  SYSTEM_MANAGED_COLUMNS,
7712
7251
  SchemaRegistry,
7713
- SecretsRoom,
7714
7252
  USERS_COLUMNS,
7715
7253
  VOTING_SCHEMAS,
7716
7254
  WORKSPACE_SCHEMAS,
@@ -7745,7 +7283,6 @@ export {
7745
7283
  createDeepSpaceAuth,
7746
7284
  createEncoder,
7747
7285
  createScopedR2Handler,
7748
- createSecrets,
7749
7286
  dataToColumnValues,
7750
7287
  decodeJwtPayload,
7751
7288
  deleteChatCascade,