deepspace 0.4.5 → 0.5.1

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 (37) hide show
  1. package/README.md +90 -0
  2. package/dist/cli.js +3590 -1485
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.d.ts +876 -3664
  5. package/dist/index.js +511 -366
  6. package/dist/index.js.map +1 -1
  7. package/dist/server.d.ts +132 -61
  8. package/dist/server.js +149 -110
  9. package/dist/server.js.map +1 -1
  10. package/dist/testing.js.map +1 -1
  11. package/dist/worker.d.ts +128 -59
  12. package/dist/worker.js +144 -99
  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/editor/useDocEditor.ts +5 -1
  19. package/features/docs/src/use-yjs-room-with-awareness.ts +4 -0
  20. package/features/file-attachments/src/AttachmentsPage.tsx +7 -1
  21. package/features/file-attachments/src/useFilePreview.ts +7 -5
  22. package/features/file-manager/src/FileManagerPage.tsx +2 -3
  23. package/features/integration-test/feature.json +10 -3
  24. package/features/integration-test/src/IntegrationTestPage.tsx +15 -12
  25. package/features/landing/src/LandingPage.tsx +1 -1
  26. package/features/landing/src/sections/FeaturesGridSection.tsx +0 -92
  27. package/features/messaging/src/ChatPage.tsx +6 -1
  28. package/features/messaging/src/components/chat/MessageItem.tsx +5 -1
  29. package/features/messaging/src/hooks/useChatChannel.ts +23 -7
  30. package/features/presence-test/feature.json +2 -1
  31. package/features/sidebar/src/AppSidebar.tsx +1 -1
  32. package/features/tasks/src/TasksPage.tsx +1 -1
  33. package/features/testing/feature.json +2 -1
  34. package/features/testing/src/TestPage.tsx +8 -8
  35. package/package.json +8 -4
  36. package/scripts/add-feature.cjs +296 -282
  37. package/dist/testing.d.ts +0 -115
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;
@@ -667,13 +669,19 @@ async function composioTools(env, options) {
667
669
  return tools;
668
670
  }
669
671
 
672
+ // src/server/utils/app-identity.ts
673
+ function appendAppIdentity(headers, env) {
674
+ headers.delete("x-app-identity-token");
675
+ headers.delete("x-app-name");
676
+ if (!env.APP_IDENTITY_TOKEN) return;
677
+ headers.set("x-app-identity-token", env.APP_IDENTITY_TOKEN);
678
+ headers.set("x-app-name", env.APP_NAME);
679
+ }
680
+
670
681
  // src/server/utils/screenshot.ts
671
682
  async function captureScreenshot(env, opts) {
672
- const headers = new Headers({
673
- "content-type": "application/json",
674
- "x-app-identity-token": env.APP_IDENTITY_TOKEN,
675
- "x-app-name": env.APP_NAME
676
- });
683
+ const headers = new Headers({ "content-type": "application/json" });
684
+ appendAppIdentity(headers, env);
677
685
  const res = await platformWorkerFetch(
678
686
  env,
679
687
  new Request("https://platform-internal/internal/screenshot", {
@@ -1333,7 +1341,7 @@ var BaseRoom = class {
1333
1341
  console.error(`[${this.constructor.name}] Message error:`, e);
1334
1342
  }
1335
1343
  }
1336
- async webSocketClose(ws, code, reason) {
1344
+ async webSocketClose(ws, _code, _reason) {
1337
1345
  const attachment = ws.deserializeAttachment();
1338
1346
  if (attachment) {
1339
1347
  try {
@@ -1358,12 +1366,12 @@ var BaseRoom = class {
1358
1366
  * Return an augmented attachment to serialize on the WebSocket,
1359
1367
  * or void to use the default attachment.
1360
1368
  */
1361
- onConnect(ws, user) {
1369
+ onConnect(_ws, _user) {
1362
1370
  }
1363
1371
  /**
1364
1372
  * Called when a WebSocket disconnects.
1365
1373
  */
1366
- onDisconnect(ws, user) {
1374
+ onDisconnect(_ws, _user) {
1367
1375
  }
1368
1376
  // ==========================================================================
1369
1377
  // Utilities
@@ -1611,7 +1619,7 @@ function coerceValue(value, storage, interpretation) {
1611
1619
  if (m) return parseFloat(m[1]) / 100;
1612
1620
  }
1613
1621
  if (interpretation.kind === "currency") {
1614
- const cleaned2 = s.replace(/[^0-9.\-]/g, "");
1622
+ const cleaned2 = s.replace(/[^0-9.-]/g, "");
1615
1623
  const n2 = parseFloat(cleaned2);
1616
1624
  if (!isNaN(n2)) return n2;
1617
1625
  return s;
@@ -1801,9 +1809,6 @@ var SchemaRegistry = class {
1801
1809
  has(name) {
1802
1810
  return this.trusted.has(name);
1803
1811
  }
1804
- hasTrusted(name) {
1805
- return this.trusted.has(name);
1806
- }
1807
1812
  all() {
1808
1813
  return Array.from(this.trusted.values());
1809
1814
  }
@@ -2118,7 +2123,7 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
2118
2123
  const columns = getResolvedColumns(schema);
2119
2124
  const existing = getRecord(ctx.sql, collection, recordId, schema);
2120
2125
  const isUpdate = existing !== null;
2121
- let mergedData = isUpdate ? { ...existing.data, ...data } : { ...data };
2126
+ const mergedData = isUpdate ? { ...existing.data, ...data } : { ...data };
2122
2127
  for (const col of columns) {
2123
2128
  if (col.readonly) continue;
2124
2129
  if (!isUpdate) {
@@ -2173,7 +2178,10 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
2173
2178
  const permCtx = ctx.getPermissionContext();
2174
2179
  if (isUpdate) {
2175
2180
  if (!canUpdate(schema, userRole, { ...existing, recordId }, userId, permCtx)) {
2176
- return { success: false, error: `UPDATE DENIED: role=${userRole}, collection=${collection}` };
2181
+ return {
2182
+ success: false,
2183
+ error: `UPDATE DENIED: role=${userRole}, collection=${collection}`
2184
+ };
2177
2185
  }
2178
2186
  const fieldError = checkFieldPermissions(schema, userRole, finalData, existing.data);
2179
2187
  if (fieldError) {
@@ -2181,7 +2189,10 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
2181
2189
  }
2182
2190
  } else {
2183
2191
  if (!canCreate(schema, userRole)) {
2184
- return { success: false, error: `CREATE DENIED: role=${userRole}, collection=${collection}` };
2192
+ return {
2193
+ success: false,
2194
+ error: `CREATE DENIED: role=${userRole}, collection=${collection}`
2195
+ };
2185
2196
  }
2186
2197
  }
2187
2198
  }
@@ -2203,7 +2214,10 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
2203
2214
  );
2204
2215
  if (existing2.toArray().length > 0) {
2205
2216
  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}` };
2217
+ return {
2218
+ success: false,
2219
+ error: `Duplicate: a record with ${fields} already exists in ${collection}`
2220
+ };
2207
2221
  }
2208
2222
  }
2209
2223
  if (isUpdate) {
@@ -2217,13 +2231,16 @@ function putRecord(ctx, collection, recordId, data, userId, userRole, skipUserRb
2217
2231
  ctx.sql.exec(`UPDATE "${tbl}" SET ${setClauses.join(", ")} WHERE _row_id = ?`, ...params);
2218
2232
  } else {
2219
2233
  const colIds = Object.keys(colValues);
2220
- const allCols = ["_row_id", "_created_by", "_created_at", "_updated_at", ...colIds.map((c) => `"${c}"`)];
2234
+ const allCols = [
2235
+ "_row_id",
2236
+ "_created_by",
2237
+ "_created_at",
2238
+ "_updated_at",
2239
+ ...colIds.map((c) => `"${c}"`)
2240
+ ];
2221
2241
  const placeholders = allCols.map(() => "?").join(", ");
2222
2242
  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
- );
2243
+ ctx.sql.exec(`INSERT INTO "${tbl}" (${allCols.join(", ")}) VALUES (${placeholders})`, ...params);
2227
2244
  }
2228
2245
  const saved = getRecord(ctx.sql, collection, recordId, schema);
2229
2246
  const record = {
@@ -3456,9 +3473,21 @@ var RecordRoom = class extends BaseRoom {
3456
3473
  ownerUserId;
3457
3474
  /** True until the first fetch() completes — detects hibernation wake-up */
3458
3475
  freshConstruct = true;
3476
+ /**
3477
+ * Per-connection `[DO Perf]` timing logs are noisy on every hot path, so
3478
+ * they're gated behind a `DEEPSPACE_DO_PERF` env binding (set it to any
3479
+ * truthy value on the worker to opt in). Off by default.
3480
+ */
3481
+ get perfLogEnabled() {
3482
+ return !!this.env?.DEEPSPACE_DO_PERF;
3483
+ }
3459
3484
  constructor(state, env, schemas = [], config = {}) {
3460
3485
  super(state, env);
3461
- this.schemaRegistry = new SchemaRegistry([...SYSTEM_COLLECTION_SCHEMAS, BASE_USERS_SCHEMA, ...schemas]);
3486
+ this.schemaRegistry = new SchemaRegistry([
3487
+ ...SYSTEM_COLLECTION_SCHEMAS,
3488
+ BASE_USERS_SCHEMA,
3489
+ ...schemas
3490
+ ]);
3462
3491
  this.ownerUserId = config.ownerUserId ?? null;
3463
3492
  const wsCount = this.state.getWebSockets().length;
3464
3493
  if (wsCount > 0) {
@@ -3542,10 +3571,7 @@ var RecordRoom = class extends BaseRoom {
3542
3571
  const now = (/* @__PURE__ */ new Date()).toISOString();
3543
3572
  for (const row of oldUsers) {
3544
3573
  const r = row;
3545
- const existing = this.sql.exec(
3546
- `SELECT 1 FROM c_users WHERE _row_id = ?`,
3547
- r.user_id
3548
- ).toArray();
3574
+ const existing = this.sql.exec(`SELECT 1 FROM c_users WHERE _row_id = ?`, r.user_id).toArray();
3549
3575
  if (existing.length > 0) continue;
3550
3576
  this.sql.exec(
3551
3577
  `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 +3605,6 @@ var RecordRoom = class extends BaseRoom {
3579
3605
  }
3580
3606
  let migrated = 0;
3581
3607
  let skipped = 0;
3582
- const now = (/* @__PURE__ */ new Date()).toISOString();
3583
3608
  for (const row of rows) {
3584
3609
  const r = row;
3585
3610
  const schema = this.schemaRegistry.get(r.collection);
@@ -3588,17 +3613,11 @@ var RecordRoom = class extends BaseRoom {
3588
3613
  continue;
3589
3614
  }
3590
3615
  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;
3616
+ const tblExists = this.sql.exec(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`, tbl).toArray().length > 0;
3595
3617
  if (!tblExists) {
3596
3618
  this.ensureCollectionTable(schema);
3597
3619
  }
3598
- const existing = this.sql.exec(
3599
- `SELECT 1 FROM "${tbl}" WHERE _row_id = ?`,
3600
- r.record_id
3601
- ).toArray();
3620
+ const existing = this.sql.exec(`SELECT 1 FROM "${tbl}" WHERE _row_id = ?`, r.record_id).toArray();
3602
3621
  if (existing.length > 0) {
3603
3622
  skipped++;
3604
3623
  continue;
@@ -3613,9 +3632,21 @@ var RecordRoom = class extends BaseRoom {
3613
3632
  const columns = schema.columns.map(resolveColumn);
3614
3633
  const colValues = dataToColumnValues(data, columns);
3615
3634
  const colIds = Object.keys(colValues);
3616
- const allCols = ["_row_id", "_created_by", "_created_at", "_updated_at", ...colIds.map((c) => `"${c}"`)];
3635
+ const allCols = [
3636
+ "_row_id",
3637
+ "_created_by",
3638
+ "_created_at",
3639
+ "_updated_at",
3640
+ ...colIds.map((c) => `"${c}"`)
3641
+ ];
3617
3642
  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])];
3643
+ const params = [
3644
+ r.record_id,
3645
+ r.created_by,
3646
+ r.created_at,
3647
+ r.updated_at,
3648
+ ...colIds.map((c) => colValues[c])
3649
+ ];
3619
3650
  this.sql.exec(
3620
3651
  `INSERT INTO "${tbl}" (${allCols.join(", ")}) VALUES (${placeholders})`,
3621
3652
  ...params
@@ -3623,7 +3654,9 @@ var RecordRoom = class extends BaseRoom {
3623
3654
  migrated++;
3624
3655
  }
3625
3656
  if (migrated > 0 || skipped > 0) {
3626
- console.log(`[RecordRoom] Migrated ${migrated} records from document-mode, ${skipped} skipped`);
3657
+ console.log(
3658
+ `[RecordRoom] Migrated ${migrated} records from document-mode, ${skipped} skipped`
3659
+ );
3627
3660
  }
3628
3661
  this.sql.exec(`DROP TABLE IF EXISTS records`);
3629
3662
  } catch (error) {
@@ -3646,7 +3679,12 @@ var RecordRoom = class extends BaseRoom {
3646
3679
  // ============================================================================
3647
3680
  async onConnect(ws, user) {
3648
3681
  await this.ensureInitialized();
3649
- const timing = this._fetchTiming ?? { fetchStart: Date.now(), isColdStart: false, needsInit: false, initMs: 0 };
3682
+ const timing = this._fetchTiming ?? {
3683
+ fetchStart: Date.now(),
3684
+ isColdStart: false,
3685
+ needsInit: false,
3686
+ initMs: 0
3687
+ };
3650
3688
  const userId = user.userId;
3651
3689
  const userName = user.userName;
3652
3690
  const userEmail = user.userEmail;
@@ -3657,7 +3695,16 @@ var RecordRoom = class extends BaseRoom {
3657
3695
  const isOwner2 = this.ownerUserId != null && userId === this.ownerUserId;
3658
3696
  const regStart = Date.now();
3659
3697
  const defaultRole = this.schemaRegistry.get("users")?.defaultRole ?? "member";
3660
- const registeredUser = await registerUser(this.sql, userId, userName, userEmail, userImageUrl, isOwner2, defaultRole, this.schemaRegistry);
3698
+ const registeredUser = await registerUser(
3699
+ this.sql,
3700
+ userId,
3701
+ userName,
3702
+ userEmail,
3703
+ userImageUrl,
3704
+ isOwner2,
3705
+ defaultRole,
3706
+ this.schemaRegistry
3707
+ );
3661
3708
  const regMs = Date.now() - regStart;
3662
3709
  attachment = {
3663
3710
  userId: registeredUser.id,
@@ -3671,7 +3718,10 @@ var RecordRoom = class extends BaseRoom {
3671
3718
  };
3672
3719
  this.send(ws, { type: MSG.USER_INFO, payload: registeredUser });
3673
3720
  const totalMs = Date.now() - timing.fetchStart;
3674
- console.log(`[DO Perf] cold=${timing.isColdStart} | init: ${timing.initMs}ms reg: ${regMs}ms | total: ${totalMs}ms`);
3721
+ if (this.perfLogEnabled)
3722
+ console.log(
3723
+ `[DO Perf] cold=${timing.isColdStart} | init: ${timing.initMs}ms reg: ${regMs}ms | total: ${totalMs}ms`
3724
+ );
3675
3725
  } else {
3676
3726
  attachment = {
3677
3727
  userId,
@@ -3682,63 +3732,37 @@ var RecordRoom = class extends BaseRoom {
3682
3732
  yjsSubscriptions: [],
3683
3733
  yjsClientId: this.nextYjsClientId++
3684
3734
  };
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
- } });
3735
+ this.send(ws, {
3736
+ type: MSG.USER_INFO,
3737
+ payload: {
3738
+ id: userId,
3739
+ name: "Anonymous",
3740
+ email: "",
3741
+ role: ROLE_ANONYMOUS,
3742
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3743
+ lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
3744
+ }
3745
+ });
3693
3746
  const totalMs = Date.now() - timing.fetchStart;
3694
- console.log(`[DO Perf] cold=${timing.isColdStart} anon | total: ${totalMs}ms`);
3747
+ if (this.perfLogEnabled)
3748
+ console.log(`[DO Perf] cold=${timing.isColdStart} anon | total: ${totalMs}ms`);
3695
3749
  }
3696
3750
  this._fetchTiming = null;
3697
3751
  return attachment;
3698
3752
  }
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
- }
3753
+ /**
3754
+ * Required to satisfy BaseRoom's abstract contract, but never invoked:
3755
+ * RecordRoom overrides `webSocketMessage`/`webSocketClose` directly (below),
3756
+ * so the BaseRoom dispatch path that would call this never runs. All real
3757
+ * message handling lives in `handleRecordMessage`.
3758
+ */
3759
+ onMessage() {
3760
+ throw new Error("RecordRoom.onMessage is unreachable; use webSocketMessage");
3738
3761
  }
3739
3762
  // ============================================================================
3740
- // Hibernation API (delegates to BaseRoom via super, but we need
3741
- // to ensure webSocketMessage gets the ensureInitialized treatment)
3763
+ // Hibernation API — RecordRoom overrides these directly (rather than using
3764
+ // BaseRoom's onMessage/onBinaryMessage/onDisconnect hooks) so it can run
3765
+ // ensureInitialized() before every message after a hibernation wake-up.
3742
3766
  // ============================================================================
3743
3767
  async webSocketMessage(ws, message) {
3744
3768
  await this.ensureInitialized();
@@ -3809,7 +3833,12 @@ var RecordRoom = class extends BaseRoom {
3809
3833
  handleUserList(userCtx, ws, attachment);
3810
3834
  break;
3811
3835
  case MSG.USER_UPDATE:
3812
- handleUserUpdate(recordCtx, ws, attachment, payload);
3836
+ handleUserUpdate(
3837
+ recordCtx,
3838
+ ws,
3839
+ attachment,
3840
+ payload
3841
+ );
3813
3842
  break;
3814
3843
  case MSG.SET_ROLE:
3815
3844
  await handleSetRole(userCtx, ws, attachment, payload);
@@ -3975,7 +4004,7 @@ var YjsRoom = class extends BaseRoom {
3975
4004
  this.sendAwarenessSnapshot(ws);
3976
4005
  return attachment;
3977
4006
  }
3978
- onMessage(ws, user, message) {
4007
+ onMessage(_ws, _user, _message) {
3979
4008
  }
3980
4009
  onBinaryMessage(ws, user, data) {
3981
4010
  const bytes = new Uint8Array(data);
@@ -3987,7 +4016,7 @@ var YjsRoom = class extends BaseRoom {
3987
4016
  this.handleAwareness(ws, decoder, bytes);
3988
4017
  }
3989
4018
  }
3990
- onDisconnect(ws, user) {
4019
+ onDisconnect(ws, _user) {
3991
4020
  const attachment = ws.deserializeAttachment();
3992
4021
  if (attachment?.awarenessClientId == null) return;
3993
4022
  const clientId = attachment.awarenessClientId;
@@ -4348,16 +4377,16 @@ var GameRoom = class extends BaseRoom {
4348
4377
  return this.currentTick;
4349
4378
  }
4350
4379
  /** Called when a player connects */
4351
- onPlayerJoin(player) {
4380
+ onPlayerJoin(_player) {
4352
4381
  }
4353
4382
  /** Called when a player disconnects */
4354
- onPlayerLeave(player) {
4383
+ onPlayerLeave(_player) {
4355
4384
  }
4356
4385
  /** Called when the game starts */
4357
4386
  onGameStart() {
4358
4387
  }
4359
4388
  /** Called when the game ends */
4360
- onGameEnd(finalState) {
4389
+ onGameEnd(_finalState) {
4361
4390
  }
4362
4391
  /**
4363
4392
  * Called once when the DO first hydrates persisted state from storage.
@@ -6817,6 +6846,18 @@ async function timingSafeEqualHex(a, b) {
6817
6846
  return diff === 0;
6818
6847
  }
6819
6848
 
6849
+ // src/server/auth/testAccounts.ts
6850
+ var TEST_ACCOUNT_EMAIL_SUFFIX = "@deepspace.test";
6851
+ function isTestAccountEmail(email) {
6852
+ return !!email && email.trim().toLowerCase().endsWith(TEST_ACCOUNT_EMAIL_SUFFIX);
6853
+ }
6854
+ function isTestAccountClaims(claims) {
6855
+ return claims?.isTestAccount === true;
6856
+ }
6857
+ function isTestAccountTier(tier) {
6858
+ return tier === "test";
6859
+ }
6860
+
6820
6861
  // src/server/auth/betterAuth.ts
6821
6862
  import { betterAuth } from "better-auth";
6822
6863
  import { organization, twoFactor } from "better-auth/plugins";
@@ -6904,11 +6945,8 @@ function isEntitled(tier, status) {
6904
6945
 
6905
6946
  // src/server/subscription.ts
6906
6947
  function appIdentityHeaders(c, extra) {
6907
- const h = new Headers({
6908
- "x-app-identity-token": c.env.APP_IDENTITY_TOKEN,
6909
- "x-app-name": c.env.APP_NAME,
6910
- ...extra ?? {}
6911
- });
6948
+ const h = new Headers(extra ?? {});
6949
+ appendAppIdentity(h, c.env);
6912
6950
  const authz = c.req.header("authorization");
6913
6951
  if (authz) h.set("authorization", authz);
6914
6952
  return h;
@@ -7006,12 +7044,9 @@ var CancelSubscriptionError = class extends Error {
7006
7044
  async function refundInvoice(c, opts) {
7007
7045
  const authz = c.req.header("authorization");
7008
7046
  const nonce = opts.requestNonce ?? crypto.randomUUID();
7009
- const headers = {
7010
- "Content-Type": "application/json",
7011
- "x-app-identity-token": c.env.APP_IDENTITY_TOKEN,
7012
- "x-app-name": c.env.APP_NAME
7013
- };
7014
- if (authz) headers["authorization"] = authz;
7047
+ const headers = new Headers({ "Content-Type": "application/json" });
7048
+ appendAppIdentity(headers, c.env);
7049
+ if (authz) headers.set("authorization", authz);
7015
7050
  const res = await apiWorkerFetch(c.env, "/api/refunds/create", {
7016
7051
  method: "POST",
7017
7052
  headers,
@@ -7075,6 +7110,7 @@ export {
7075
7110
  SchemaRegistry,
7076
7111
  SubscriptionAuthError,
7077
7112
  SubscriptionRequiredError,
7113
+ TEST_ACCOUNT_EMAIL_SUFFIX,
7078
7114
  USERS_COLUMNS,
7079
7115
  VOTING_SCHEMAS,
7080
7116
  WORKSPACE_SCHEMAS,
@@ -7139,6 +7175,9 @@ export {
7139
7175
  handleYjsLeave,
7140
7176
  isAutoProvision,
7141
7177
  isOwner,
7178
+ isTestAccountClaims,
7179
+ isTestAccountEmail,
7180
+ isTestAccountTier,
7142
7181
  lintSchema,
7143
7182
  loadMessages,
7144
7183
  makeDefaultSummarizer,