@void/platform 0.21.1 → 0.21.3

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.
@@ -30346,6 +30346,7 @@ __export(cloudflare_api_exports, {
30346
30346
  deleteKVNamespace: () => deleteKVNamespace,
30347
30347
  deleteQueue: () => deleteQueue,
30348
30348
  deleteR2Bucket: () => deleteR2Bucket,
30349
+ enableD1ReadReplication: () => enableD1ReadReplication,
30349
30350
  getCustomHostname: () => getCustomHostname,
30350
30351
  getCustomHostnameByHostname: () => getCustomHostnameByHostname,
30351
30352
  getD1DatabaseByName: () => getD1DatabaseByName,
@@ -30384,7 +30385,13 @@ async function cfFetch(url2, apiToken, init) {
30384
30385
  async function createD1Database(accountId, apiToken, name) {
30385
30386
  return cfFetch(`${CF_API_BASE}/${accountId}/d1/database`, apiToken, {
30386
30387
  method: "POST",
30387
- body: JSON.stringify({ name })
30388
+ body: JSON.stringify({ name, read_replication: { mode: "auto" } })
30389
+ });
30390
+ }
30391
+ async function enableD1ReadReplication(accountId, apiToken, databaseId) {
30392
+ await cfFetch(`${CF_API_BASE}/${accountId}/d1/database/${databaseId}`, apiToken, {
30393
+ method: "PUT",
30394
+ body: JSON.stringify({ read_replication: { mode: "auto" } })
30388
30395
  });
30389
30396
  }
30390
30397
  async function createKVNamespace(accountId, apiToken, title) {
@@ -30730,6 +30737,7 @@ var init_cloudflare_api = __esm({
30730
30737
  };
30731
30738
  __name(cfFetch, "cfFetch");
30732
30739
  __name(createD1Database, "createD1Database");
30740
+ __name(enableD1ReadReplication, "enableD1ReadReplication");
30733
30741
  __name(createKVNamespace, "createKVNamespace");
30734
30742
  __name(createR2Bucket, "createR2Bucket");
30735
30743
  __name(getD1DatabaseByName, "getD1DatabaseByName");
@@ -42022,6 +42030,7 @@ __export(schema_exports, {
42022
42030
  platformLoginTests: () => platformLoginTests,
42023
42031
  platformSettings: () => platformSettings,
42024
42032
  projectAccessEvents: () => projectAccessEvents,
42033
+ projectCleanupJobs: () => projectCleanupJobs,
42025
42034
  projectDatabaseCredentials: () => projectDatabaseCredentials,
42026
42035
  projectDeployCredentials: () => projectDeployCredentials,
42027
42036
  projectInvitations: () => projectInvitations,
@@ -42034,7 +42043,7 @@ __export(schema_exports, {
42034
42043
  sandboxDrainCheckpoints: () => sandboxDrainCheckpoints,
42035
42044
  users: () => users
42036
42045
  });
42037
- var users, oauthAccounts, platformAuthSettings, platformLoginConnections, platformLoginRevisions, platformLoginIdentities, platformHumanSessions, platformCompanyIdentities, platformLoginTests, platformAuthAttempts, platformAuthBootstrap, projects, projectDeployCredentials, projectMembers, projectInvitations, projectOwnershipPeriods, projectAccessEvents, deployments, projectSecrets, resources, domains, emailDestinations, emailDestinationCleanups, emailDomains, platformSettings, operatorSessions, operatorEvents, sandboxDrainCheckpoints, platformEvents, oauthNonces, authCodes, migrationRenames, projectMigrations, projectDatabaseCredentials, githubInstallations, githubConnections, githubInstallationMembers, githubInstallationRevocations, githubWebhookRedeliveries, builds, invitations;
42046
+ var users, oauthAccounts, platformAuthSettings, platformLoginConnections, platformLoginRevisions, platformLoginIdentities, platformHumanSessions, platformCompanyIdentities, platformLoginTests, platformAuthAttempts, platformAuthBootstrap, projects, projectDeployCredentials, projectMembers, projectInvitations, projectOwnershipPeriods, projectAccessEvents, deployments, projectCleanupJobs, projectSecrets, resources, domains, emailDestinations, emailDestinationCleanups, emailDomains, platformSettings, operatorSessions, operatorEvents, sandboxDrainCheckpoints, platformEvents, oauthNonces, authCodes, migrationRenames, projectMigrations, projectDatabaseCredentials, githubInstallations, githubConnections, githubInstallationMembers, githubInstallationRevocations, githubWebhookRedeliveries, builds, invitations;
42038
42047
  var init_schema2 = __esm({
42039
42048
  "../../home/runner/work/void/void/platform/packages/api/src/schema.ts"() {
42040
42049
  "use strict";
@@ -42415,6 +42424,20 @@ var init_schema2 = __esm({
42415
42424
  index("idx_deployments_created_id").on(table2.created_at, table2.id)
42416
42425
  ]
42417
42426
  );
42427
+ projectCleanupJobs = pgTable(
42428
+ "project_cleanup_jobs",
42429
+ {
42430
+ target: text("target").primaryKey(),
42431
+ project_id: text("project_id").notNull(),
42432
+ kind: text("kind").notNull().$type(),
42433
+ payload: jsonb("payload").notNull().$type(),
42434
+ attempts: integer("attempts").notNull().default(0),
42435
+ last_error: text("last_error"),
42436
+ next_attempt_at: timestamp("next_attempt_at", { mode: "string" }).notNull().defaultNow(),
42437
+ created_at: timestamp("created_at", { mode: "string" }).notNull().defaultNow()
42438
+ },
42439
+ (table2) => [index("project_cleanup_jobs_due").on(table2.next_attempt_at, table2.created_at)]
42440
+ );
42418
42441
  projectSecrets = pgTable(
42419
42442
  "project_secrets",
42420
42443
  {
@@ -47461,6 +47484,26 @@ var init_admission = __esm({
47461
47484
  }
47462
47485
  });
47463
47486
 
47487
+ // ../../home/runner/work/void/void/platform/packages/api/src/naming.ts
47488
+ function getLegacyProjectWorkerName(slug) {
47489
+ return `user-${slug}`;
47490
+ }
47491
+ function getProjectDeploymentWorkerName(slug, deploymentId) {
47492
+ return `user-${slug}-${deploymentId}`;
47493
+ }
47494
+ function getProjectQueueName(projectId, queueName) {
47495
+ return `${projectId}-${queueName}`.toLowerCase().replaceAll("_", "-");
47496
+ }
47497
+ var init_naming = __esm({
47498
+ "../../home/runner/work/void/void/platform/packages/api/src/naming.ts"() {
47499
+ "use strict";
47500
+ init_performance2();
47501
+ __name(getLegacyProjectWorkerName, "getLegacyProjectWorkerName");
47502
+ __name(getProjectDeploymentWorkerName, "getProjectDeploymentWorkerName");
47503
+ __name(getProjectQueueName, "getProjectQueueName");
47504
+ }
47505
+ });
47506
+
47464
47507
  // ../../home/runner/work/void/void/platform/packages/api/src/tiers.ts
47465
47508
  function getPlanLimits(plan) {
47466
47509
  return PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
@@ -47626,6 +47669,7 @@ __export(db_exports, {
47626
47669
  cancelDeployment: () => cancelDeployment,
47627
47670
  cancelQueuedContainerBuildsForUsers: () => cancelQueuedContainerBuildsForUsers,
47628
47671
  claimBuildForStartWithinLimit: () => claimBuildForStartWithinLimit,
47672
+ cleanupJobsForResource: () => cleanupJobsForResource,
47629
47673
  clearGitHubInstallationOwnerForUser: () => clearGitHubInstallationOwnerForUser,
47630
47674
  consumeAuthCode: () => consumeAuthCode,
47631
47675
  consumeCliAuthCode: () => consumeCliAuthCode,
@@ -47661,9 +47705,11 @@ __export(db_exports, {
47661
47705
  deleteProjectSecret: () => deleteProjectSecret,
47662
47706
  deleteRollbackLease: () => deleteRollbackLease,
47663
47707
  deleteUser: () => deleteUser,
47708
+ enqueueProjectCleanupJobs: () => enqueueProjectCleanupJobs,
47664
47709
  failStaleActiveContainerBuilds: () => failStaleActiveContainerBuilds,
47665
47710
  failStaleActiveGithubActionsBuilds: () => failStaleActiveGithubActionsBuilds,
47666
47711
  failStaleDeployments: () => failStaleDeployments,
47712
+ getAccessibleProjectBySlug: () => getAccessibleProjectBySlug,
47667
47713
  getAccessibleProjectsByUserId: () => getAccessibleProjectsByUserId,
47668
47714
  getActivatedDeployments: () => getActivatedDeployments,
47669
47715
  getActiveDeployment: () => getActiveDeployment,
@@ -47971,6 +48017,25 @@ async function getAccessibleProjectsByUserId(db, userId) {
47971
48017
  owner: row.owner
47972
48018
  }));
47973
48019
  }
48020
+ async function getAccessibleProjectBySlug(db, userId, slug) {
48021
+ const [row] = await db.select({ project: projects, memberRole: projectMembers.role, owner: accessibleProjectOwner }).from(projects).innerJoin(accessibleProjectOwner, eq(projects.user_id, accessibleProjectOwner.id)).leftJoin(
48022
+ projectMembers,
48023
+ and(eq(projectMembers.project_id, projects.id), eq(projectMembers.user_id, userId))
48024
+ ).where(
48025
+ and(
48026
+ eq(projects.slug, slug),
48027
+ or(eq(projects.user_id, userId), eq(projectMembers.user_id, userId))
48028
+ )
48029
+ ).limit(1);
48030
+ if (!row || isAdminSuspended(row.owner.suspended_reason)) {
48031
+ return null;
48032
+ }
48033
+ return {
48034
+ project: row.project,
48035
+ role: row.project.user_id === userId ? "owner" : row.memberRole,
48036
+ owner: row.owner
48037
+ };
48038
+ }
47974
48039
  async function getProjectMember(db, projectId, userId) {
47975
48040
  const [row] = await db.select().from(projectMembers).where(and(eq(projectMembers.project_id, projectId), eq(projectMembers.user_id, userId))).limit(1);
47976
48041
  return row ?? null;
@@ -48434,7 +48499,7 @@ async function getLatestDeploymentsByProjectIds(db, projectIds) {
48434
48499
  if (projectIds.length === 0) {
48435
48500
  return /* @__PURE__ */ new Map();
48436
48501
  }
48437
- const rows = await db.select({
48502
+ const rows = await db.selectDistinctOn([deployments.project_id], {
48438
48503
  id: deployments.id,
48439
48504
  project_id: deployments.project_id,
48440
48505
  status: deployments.status,
@@ -48442,14 +48507,8 @@ async function getLatestDeploymentsByProjectIds(db, projectIds) {
48442
48507
  commit: deployments.commit,
48443
48508
  created_at: deployments.created_at,
48444
48509
  deployed_at: deployments.deployed_at
48445
- }).from(deployments).where(inArray(deployments.project_id, projectIds)).orderBy(desc(deployments.created_at));
48446
- const latest = /* @__PURE__ */ new Map();
48447
- for (const row of rows) {
48448
- if (!latest.has(row.project_id)) {
48449
- latest.set(row.project_id, row);
48450
- }
48451
- }
48452
- return latest;
48510
+ }).from(deployments).where(inArray(deployments.project_id, projectIds)).orderBy(deployments.project_id, desc(deployments.created_at), desc(deployments.id));
48511
+ return new Map(rows.map((row) => [row.project_id, row]));
48453
48512
  }
48454
48513
  async function getProjectsByIds(db, ids) {
48455
48514
  if (ids.length === 0) {
@@ -49154,7 +49213,36 @@ async function promoteEmailDestinationWithOwnerAudit(db, projectId, id2, cfDesti
49154
49213
  async function deleteProject(db, id2) {
49155
49214
  await db.delete(projects).where(eq(projects.id, id2));
49156
49215
  }
49157
- async function deleteProjectCascade(db, projectId) {
49216
+ function cleanupJobsForResource(projectId, resource) {
49217
+ if (resource.type === "container") {
49218
+ return [];
49219
+ }
49220
+ const jobs = [
49221
+ {
49222
+ target: `cf-resource:${projectId}:${resource.type}:${resource.cf_id}`,
49223
+ project_id: projectId,
49224
+ kind: "cf-resource",
49225
+ payload: { type: resource.type, cfId: resource.cf_id }
49226
+ }
49227
+ ];
49228
+ if (resource.type === "queue") {
49229
+ const key = `queue:${getProjectQueueName(projectId, resource.binding_name)}`;
49230
+ jobs.push({
49231
+ target: `routing-key:${projectId}:${key}`,
49232
+ project_id: projectId,
49233
+ kind: "routing-key",
49234
+ payload: { key }
49235
+ });
49236
+ }
49237
+ return jobs;
49238
+ }
49239
+ async function enqueueProjectCleanupJobs(db, jobs) {
49240
+ if (jobs.length === 0) {
49241
+ return;
49242
+ }
49243
+ await db.insert(projectCleanupJobs).values([...jobs]).onConflictDoNothing();
49244
+ }
49245
+ async function deleteProjectCascade(db, projectId, processedResourceCfIds = /* @__PURE__ */ new Set()) {
49158
49246
  return await db.transaction(async (tx) => {
49159
49247
  const txDb = tx;
49160
49248
  await tx.execute(sql`SELECT id FROM ${projects} WHERE id = ${projectId} FOR UPDATE`);
@@ -49163,6 +49251,10 @@ async function deleteProjectCascade(db, projectId) {
49163
49251
  });
49164
49252
  await tx.delete(domains).where(eq(domains.project_id, projectId));
49165
49253
  const deletedResources = await tx.delete(resources).where(eq(resources.project_id, projectId)).returning();
49254
+ await enqueueProjectCleanupJobs(
49255
+ txDb,
49256
+ deletedResources.filter((resource) => !processedResourceCfIds.has(resource.cf_id)).flatMap((resource) => cleanupJobsForResource(projectId, resource))
49257
+ );
49166
49258
  await tx.delete(projectSecrets).where(eq(projectSecrets.project_id, projectId));
49167
49259
  await tx.delete(builds).where(eq(builds.project_id, projectId));
49168
49260
  await tx.delete(deployments).where(eq(deployments.project_id, projectId));
@@ -49337,7 +49429,13 @@ async function searchProjects(db, query) {
49337
49429
  last_deploy_id: lastDeploy.last_deploy_id,
49338
49430
  last_deploy_status: lastDeploy.last_deploy_status
49339
49431
  }).from(projects).leftJoin(users, eq(projects.user_id, users.id)).leftJoin(lastDeploy, eq(projects.id, lastDeploy.project_id)).where(
49340
- or(ilike(projects.slug, pattern), ilike(users.login, pattern), ilike(projects.id, pattern))
49432
+ or(
49433
+ ilike(projects.slug, pattern),
49434
+ ilike(users.login, pattern),
49435
+ ilike(projects.id, pattern),
49436
+ ilike(projects.type, pattern),
49437
+ ilike(lastDeploy.last_deploy_status, pattern)
49438
+ )
49341
49439
  ).orderBy(desc(projects.created_at)).limit(50);
49342
49440
  }
49343
49441
  async function searchDeployments(db, query) {
@@ -49353,7 +49451,8 @@ async function searchDeployments(db, query) {
49353
49451
  ilike(deployments.status, pattern),
49354
49452
  ilike(deployments.commit, pattern),
49355
49453
  ilike(deployments.source, pattern),
49356
- ilike(deployments.cli_version, pattern)
49454
+ ilike(deployments.cli_version, pattern),
49455
+ ilike(deployments.error_message, pattern)
49357
49456
  )
49358
49457
  ).orderBy(desc(deployments.created_at)).limit(50);
49359
49458
  }
@@ -49709,15 +49808,18 @@ async function getQueuedBuilds(db, limit = 50, excludeUserIds = []) {
49709
49808
  return db.select().from(oldestPerProject).orderBy(asc(oldestPerProject.created_at)).limit(limit);
49710
49809
  }
49711
49810
  async function getUsersWithQueuedContainerBuilds(db) {
49811
+ const activeBuilds = alias(builds, "active_builds");
49712
49812
  const rows = await db.select({
49713
49813
  userId: users.id,
49714
49814
  plan: users.plan,
49715
49815
  suspendedReason: users.suspended_reason,
49716
- // Correlated count of this user's in-flight builds (any executor). The
49717
- // FILTER over a LEFT JOIN keeps users whose only builds are queued
49718
- // (activeBuilds = 0) in the result.
49719
- activeBuilds: sql`count(${builds.id}) filter (
49720
- where ${inArray(builds.status, ACTIVE_BUILD_STATUSES)}
49816
+ // Count only active rows through idx_builds_user_status. Joining the
49817
+ // entire builds table here would touch every historical build belonging
49818
+ // to each queued owner on every scheduler tick.
49819
+ activeBuilds: sql`(
49820
+ select count(*) from ${builds} as active_builds
49821
+ where ${activeBuilds.user_id} = ${users.id}
49822
+ and ${inArray(activeBuilds.status, ACTIVE_BUILD_STATUSES)}
49721
49823
  )`
49722
49824
  }).from(users).innerJoin(
49723
49825
  // Restrict to users who own at least one queued container build — the
@@ -49727,7 +49829,7 @@ async function getUsersWithQueuedContainerBuilds(db) {
49727
49829
  where ${builds.status} = 'queued' and ${builds.executor} = 'container'
49728
49830
  ) as queued_owners`,
49729
49831
  sql`queued_owners.user_id = ${users.id}`
49730
- ).leftJoin(builds, eq(builds.user_id, users.id)).groupBy(users.id, users.plan);
49832
+ );
49731
49833
  return rows.map((r) => ({
49732
49834
  userId: r.userId,
49733
49835
  plan: r.plan,
@@ -50548,6 +50650,7 @@ var init_db2 = __esm({
50548
50650
  init_configuration();
50549
50651
  init_admission();
50550
50652
  init_id2();
50653
+ init_naming();
50551
50654
  init_project_access();
50552
50655
  init_schema2();
50553
50656
  init_schema2();
@@ -50606,6 +50709,7 @@ var init_db2 = __esm({
50606
50709
  __name(getOwnedProjectsByUserId, "getOwnedProjectsByUserId");
50607
50710
  accessibleProjectOwner = alias(users, "accessible_project_owner");
50608
50711
  __name(getAccessibleProjectsByUserId, "getAccessibleProjectsByUserId");
50712
+ __name(getAccessibleProjectBySlug, "getAccessibleProjectBySlug");
50609
50713
  __name(getProjectMember, "getProjectMember");
50610
50714
  __name(getProjectMembers, "getProjectMembers");
50611
50715
  ProjectTeamMutationError = class extends Error {
@@ -50715,6 +50819,8 @@ var init_db2 = __esm({
50715
50819
  __name(queueUnreferencedEmailDestinationCleanup, "queueUnreferencedEmailDestinationCleanup");
50716
50820
  __name(promoteEmailDestinationWithOwnerAudit, "promoteEmailDestinationWithOwnerAudit");
50717
50821
  __name(deleteProject, "deleteProject");
50822
+ __name(cleanupJobsForResource, "cleanupJobsForResource");
50823
+ __name(enqueueProjectCleanupJobs, "enqueueProjectCleanupJobs");
50718
50824
  __name(deleteProjectCascade, "deleteProjectCascade");
50719
50825
  __name(countUsers, "countUsers");
50720
50826
  __name(countProjects, "countProjects");
@@ -50841,6 +50947,28 @@ var init_env = __esm({
50841
50947
  }
50842
50948
  });
50843
50949
 
50950
+ // ../../home/runner/work/void/void/platform/packages/api/src/concurrency.ts
50951
+ async function uploadPool(items, concurrency, fn) {
50952
+ let nextIndex = 0;
50953
+ async function worker() {
50954
+ while (nextIndex < items.length) {
50955
+ const item = items[nextIndex++];
50956
+ await fn(item);
50957
+ }
50958
+ }
50959
+ __name(worker, "worker");
50960
+ await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
50961
+ }
50962
+ var R2_CONCURRENCY;
50963
+ var init_concurrency = __esm({
50964
+ "../../home/runner/work/void/void/platform/packages/api/src/concurrency.ts"() {
50965
+ "use strict";
50966
+ init_performance2();
50967
+ R2_CONCURRENCY = 6;
50968
+ __name(uploadPool, "uploadPool");
50969
+ }
50970
+ });
50971
+
50844
50972
  // ../../home/runner/work/void/void/platform/packages/api/src/routes/project-access.ts
50845
50973
  async function requireProjectRouteCapability(context, projectId, capability, db = context.get("db")) {
50846
50974
  try {
@@ -50890,7 +51018,7 @@ async function queryProjectUsage(accountId, apiToken, requestsDataset, projectId
50890
51018
  }
50891
51019
  );
50892
51020
  if (!response.ok) {
50893
- return { requests: 0, bandwidthBytes: 0 };
51021
+ throw new Error(`Usage query failed with HTTP ${response.status}`);
50894
51022
  }
50895
51023
  const result = await response.json();
50896
51024
  const row = result.data?.[0];
@@ -50936,7 +51064,7 @@ async function getAccountUsageProjectWindows(db, userId, since, until) {
50936
51064
  function analyticsStringLiteral(value) {
50937
51065
  return `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'`;
50938
51066
  }
50939
- async function queryBatchProjectUsageWindows(accountId, apiToken, requestsDataset, windows) {
51067
+ async function queryBatchProjectUsageWindows(accountId, apiToken, requestsDataset, windows, failOnError = false) {
50940
51068
  if (windows.length === 0) {
50941
51069
  return /* @__PURE__ */ new Map();
50942
51070
  }
@@ -50945,39 +51073,63 @@ async function queryBatchProjectUsageWindows(accountId, apiToken, requestsDatase
50945
51073
  ({ start, end }) => `(blob1 = ${analyticsStringLiteral(project.id)} AND timestamp >= toDateTime64(${analyticsStringLiteral(formatAnalyticsDateMilliseconds(start))}, 3) AND timestamp < toDateTime64(${analyticsStringLiteral(formatAnalyticsDateMilliseconds(end))}, 3))`
50946
51074
  )
50947
51075
  );
50948
- const query = `
51076
+ const usage = /* @__PURE__ */ new Map();
51077
+ for (let offset = 0; offset < predicates.length; offset += 50) {
51078
+ const query = `
50949
51079
  SELECT
50950
51080
  blob1,
50951
51081
  SUM(_sample_interval * double1) as requests,
50952
51082
  SUM(_sample_interval * double2) as bandwidth_bytes
50953
51083
  FROM ${requestsDataset}
50954
- WHERE ${predicates.join("\n OR ")}
51084
+ WHERE ${predicates.slice(offset, offset + 50).join("\n OR ")}
50955
51085
  GROUP BY blob1
50956
51086
  `;
50957
- const response = await fetch(
50958
- `https://api.cloudflare.com/client/v4/accounts/${accountId}/analytics_engine/sql`,
50959
- {
50960
- method: "POST",
50961
- headers: {
50962
- Authorization: `Bearer ${apiToken}`,
50963
- "Content-Type": "text/plain"
50964
- },
50965
- body: query
50966
- }
50967
- );
50968
- if (!response.ok) {
50969
- return /* @__PURE__ */ new Map();
50970
- }
50971
- const json3 = await response.json();
50972
- return new Map(
50973
- (json3.data ?? []).map((row) => [
50974
- row.blob1,
51087
+ const response = await fetch(
51088
+ `https://api.cloudflare.com/client/v4/accounts/${accountId}/analytics_engine/sql`,
50975
51089
  {
50976
- requests: Math.round(row.requests ?? 0),
50977
- bandwidthBytes: Math.round(row.bandwidth_bytes ?? 0)
51090
+ method: "POST",
51091
+ headers: {
51092
+ Authorization: `Bearer ${apiToken}`,
51093
+ "Content-Type": "text/plain"
51094
+ },
51095
+ body: query
50978
51096
  }
50979
- ])
51097
+ );
51098
+ if (!response.ok) {
51099
+ if (failOnError) {
51100
+ throw new Error(`Usage query failed with HTTP ${response.status}`);
51101
+ }
51102
+ return /* @__PURE__ */ new Map();
51103
+ }
51104
+ const json3 = await response.json();
51105
+ for (const row of json3.data ?? []) {
51106
+ const previous = usage.get(row.blob1) ?? ZERO_USAGE;
51107
+ usage.set(row.blob1, {
51108
+ requests: previous.requests + Math.round(row.requests ?? 0),
51109
+ bandwidthBytes: previous.bandwidthBytes + Math.round(row.bandwidth_bytes ?? 0)
51110
+ });
51111
+ }
51112
+ }
51113
+ return usage;
51114
+ }
51115
+ async function sumAiUsageForWindows(accountId, apiToken, gatewayId, windows) {
51116
+ const totals = windows.map(() => 0);
51117
+ const tasks = windows.flatMap(
51118
+ ({ projectId, intervals }, index2) => intervals.map((interval2) => ({ projectId, interval: interval2, index: index2 }))
50980
51119
  );
51120
+ await uploadPool(tasks, 4, async ({ projectId, interval: interval2, index: index2 }) => {
51121
+ const usage = await queryProjectAiUsage(
51122
+ accountId,
51123
+ apiToken,
51124
+ gatewayId,
51125
+ projectId,
51126
+ interval2.start,
51127
+ interval2.end,
51128
+ true
51129
+ );
51130
+ totals[index2] += usage.neurons;
51131
+ });
51132
+ return totals;
50981
51133
  }
50982
51134
  async function queryTopProjectTraffic(accountId, apiToken, requestsDataset, since, limit) {
50983
51135
  if (limit <= 0) {
@@ -51217,6 +51369,7 @@ var init_usage = __esm({
51217
51369
  init_performance2();
51218
51370
  init_drizzle_orm();
51219
51371
  init_dist2();
51372
+ init_concurrency();
51220
51373
  init_env();
51221
51374
  init_schema2();
51222
51375
  init_tiers();
@@ -51238,22 +51391,30 @@ var init_usage = __esm({
51238
51391
  }
51239
51392
  const since = getCurrentBillingPeriodStart(access.owner.billing_cycle_start);
51240
51393
  const requestsDataset = getRequestsDataset(context.env);
51241
- const [usage, aiUsage] = await Promise.all([
51242
- queryProjectUsage(
51243
- context.env.CF_ACCOUNT_ID,
51244
- context.env.CF_API_TOKEN,
51245
- requestsDataset,
51246
- projectId,
51247
- since
51248
- ),
51249
- queryProjectAiUsage(
51250
- context.env.CF_ACCOUNT_ID,
51251
- context.env.CF_API_TOKEN,
51252
- context.env.AI_GATEWAY_ID ?? "void",
51253
- projectId,
51254
- since
51255
- )
51256
- ]);
51394
+ let usage;
51395
+ let aiUsage;
51396
+ try {
51397
+ [usage, aiUsage] = await Promise.all([
51398
+ queryProjectUsage(
51399
+ context.env.CF_ACCOUNT_ID,
51400
+ context.env.CF_API_TOKEN,
51401
+ requestsDataset,
51402
+ projectId,
51403
+ since
51404
+ ),
51405
+ queryProjectAiUsage(
51406
+ context.env.CF_ACCOUNT_ID,
51407
+ context.env.CF_API_TOKEN,
51408
+ context.env.AI_GATEWAY_ID ?? "void",
51409
+ projectId,
51410
+ since,
51411
+ void 0,
51412
+ true
51413
+ )
51414
+ ]);
51415
+ } catch {
51416
+ return context.json({ error: "usage unavailable" }, 503);
51417
+ }
51257
51418
  return context.json({
51258
51419
  projectId,
51259
51420
  billingPeriodStart: since.toISOString(),
@@ -51289,38 +51450,32 @@ var init_usage = __esm({
51289
51450
  });
51290
51451
  }
51291
51452
  const requestsDataset = getRequestsDataset(context.env);
51292
- const [usageMap, ...aiUsages] = await Promise.all([
51293
- queryBatchProjectUsageWindows(
51294
- context.env.CF_ACCOUNT_ID,
51295
- context.env.CF_API_TOKEN,
51296
- requestsDataset,
51297
- projectWindows
51298
- ),
51299
- ...projectWindows.map(
51300
- async ({ project, intervals }) => intervals.reduce(
51301
- async (totalPromise, interval2) => {
51302
- const [total, usage] = await Promise.all([
51303
- totalPromise,
51304
- queryProjectAiUsage(
51305
- context.env.CF_ACCOUNT_ID,
51306
- context.env.CF_API_TOKEN,
51307
- context.env.AI_GATEWAY_ID ?? "void",
51308
- project.id,
51309
- interval2.start,
51310
- interval2.end
51311
- )
51312
- ]);
51313
- return { neurons: total.neurons + usage.neurons };
51314
- },
51315
- Promise.resolve({ neurons: 0 })
51453
+ let usageMap;
51454
+ let aiUsages;
51455
+ try {
51456
+ [usageMap, aiUsages] = await Promise.all([
51457
+ queryBatchProjectUsageWindows(
51458
+ context.env.CF_ACCOUNT_ID,
51459
+ context.env.CF_API_TOKEN,
51460
+ requestsDataset,
51461
+ projectWindows,
51462
+ true
51463
+ ),
51464
+ sumAiUsageForWindows(
51465
+ context.env.CF_ACCOUNT_ID,
51466
+ context.env.CF_API_TOKEN,
51467
+ context.env.AI_GATEWAY_ID ?? "void",
51468
+ projectWindows.map(({ project, intervals }) => ({ projectId: project.id, intervals }))
51316
51469
  )
51317
- )
51318
- ]);
51470
+ ]);
51471
+ } catch {
51472
+ return context.json({ error: "usage unavailable" }, 503);
51473
+ }
51319
51474
  const projectUsages = projectWindows.map(({ project }, i) => ({
51320
51475
  projectId: project.id,
51321
51476
  slug: project.slug,
51322
51477
  ...usageMap.get(project.id) ?? ZERO_USAGE,
51323
- aiNeurons: aiUsages[i].neurons
51478
+ aiNeurons: aiUsages[i]
51324
51479
  }));
51325
51480
  const totals = projectUsages.reduce(
51326
51481
  (acc, u) => ({
@@ -51343,6 +51498,7 @@ var init_usage = __esm({
51343
51498
  __name(getAccountUsageProjectWindows, "getAccountUsageProjectWindows");
51344
51499
  __name(analyticsStringLiteral, "analyticsStringLiteral");
51345
51500
  __name(queryBatchProjectUsageWindows, "queryBatchProjectUsageWindows");
51501
+ __name(sumAiUsageForWindows, "sumAiUsageForWindows");
51346
51502
  __name(queryTopProjectTraffic, "queryTopProjectTraffic");
51347
51503
  __name(formatAnalyticsDate, "formatAnalyticsDate");
51348
51504
  __name(formatAnalyticsDateMilliseconds, "formatAnalyticsDateMilliseconds");
@@ -56273,26 +56429,6 @@ var init_cron_scheduler_client = __esm({
56273
56429
  }
56274
56430
  });
56275
56431
 
56276
- // ../../home/runner/work/void/void/platform/packages/api/src/naming.ts
56277
- function getLegacyProjectWorkerName(slug) {
56278
- return `user-${slug}`;
56279
- }
56280
- function getProjectDeploymentWorkerName(slug, deploymentId) {
56281
- return `user-${slug}-${deploymentId}`;
56282
- }
56283
- function getProjectQueueName(projectId, queueName) {
56284
- return `${projectId}-${queueName}`.toLowerCase().replaceAll("_", "-");
56285
- }
56286
- var init_naming = __esm({
56287
- "../../home/runner/work/void/void/platform/packages/api/src/naming.ts"() {
56288
- "use strict";
56289
- init_performance2();
56290
- __name(getLegacyProjectWorkerName, "getLegacyProjectWorkerName");
56291
- __name(getProjectDeploymentWorkerName, "getProjectDeploymentWorkerName");
56292
- __name(getProjectQueueName, "getProjectQueueName");
56293
- }
56294
- });
56295
-
56296
56432
  // ../../home/runner/work/void/void/platform/packages/api/src/sandbox-drain.ts
56297
56433
  async function writeSandboxDrainMirror(routing, checkpoint) {
56298
56434
  const state = {
@@ -57048,6 +57184,97 @@ function deleteCloudflareResource(accountId, apiToken, resource) {
57048
57184
  throw new Error(`Unsupported resource type: ${resourceType}`);
57049
57185
  }
57050
57186
  }
57187
+ function cleanupJob(projectId, kind, target, payload) {
57188
+ return { project_id: projectId, kind, target: `${kind}:${projectId}:${target}`, payload };
57189
+ }
57190
+ function jobValue(job, key) {
57191
+ const value = job.payload[key];
57192
+ if (!value) {
57193
+ throw new Error(`Project cleanup job ${job.target} is missing ${key}`);
57194
+ }
57195
+ return value;
57196
+ }
57197
+ async function ignoreMissingProviderObject(operation) {
57198
+ try {
57199
+ await operation();
57200
+ } catch (error63) {
57201
+ if (error63 instanceof CloudflareApiError && error63.status === 404) {
57202
+ return;
57203
+ }
57204
+ throw error63;
57205
+ }
57206
+ }
57207
+ async function executeCleanupJob(env2, job) {
57208
+ switch (job.kind) {
57209
+ case "cf-resource":
57210
+ await ignoreMissingProviderObject(
57211
+ () => deleteCloudflareResource(env2.CF_ACCOUNT_ID, env2.CF_API_TOKEN, {
57212
+ type: jobValue(job, "type"),
57213
+ cf_id: jobValue(job, "cfId")
57214
+ })
57215
+ );
57216
+ return;
57217
+ case "worker": {
57218
+ const namespace = jobValue(job, "namespace");
57219
+ const name = jobValue(job, "name");
57220
+ const response = await fetch(
57221
+ `https://api.cloudflare.com/client/v4/accounts/${env2.CF_ACCOUNT_ID}/workers/dispatch/namespaces/${namespace}/scripts/${name}`,
57222
+ { method: "DELETE", headers: { Authorization: `Bearer ${env2.CF_API_TOKEN}` } }
57223
+ );
57224
+ if (!response.ok && response.status !== 404) {
57225
+ throw new Error(`Worker delete returned HTTP ${response.status}: ${await response.text()}`);
57226
+ }
57227
+ void response.body?.cancel().catch(() => {
57228
+ });
57229
+ return;
57230
+ }
57231
+ case "worker-bundle":
57232
+ await deleteWorkerBundle(
57233
+ env2.STATIC_ASSETS_R2,
57234
+ jobValue(job, "projectId"),
57235
+ jobValue(job, "deploymentId")
57236
+ );
57237
+ return;
57238
+ case "project-assets":
57239
+ await deleteProjectAssets(env2.STATIC_ASSETS_R2, jobValue(job, "projectId"));
57240
+ return;
57241
+ case "routing-key":
57242
+ await env2.ROUTING.delete(jobValue(job, "key"));
57243
+ return;
57244
+ case "cron":
57245
+ await clearCronScheduler(env2, jobValue(job, "projectId"));
57246
+ return;
57247
+ case "custom-hostname":
57248
+ await ignoreMissingProviderObject(
57249
+ () => deleteCustomHostname(
57250
+ jobValue(job, "zoneId"),
57251
+ env2.CF_API_TOKEN,
57252
+ jobValue(job, "hostnameId")
57253
+ )
57254
+ );
57255
+ return;
57256
+ }
57257
+ }
57258
+ async function runCleanupBatch(env2, jobs) {
57259
+ const warnings = [];
57260
+ const failed = [];
57261
+ let next = 0;
57262
+ await Promise.all(
57263
+ Array.from({ length: Math.min(5, jobs.length) }, async () => {
57264
+ while (next < jobs.length) {
57265
+ const job = jobs[next++];
57266
+ try {
57267
+ await executeCleanupJob(env2, job);
57268
+ } catch (error63) {
57269
+ const message2 = error63 instanceof Error ? error63.message : String(error63);
57270
+ warnings.push(`Failed to delete ${job.target}: ${message2}`);
57271
+ failed.push(job);
57272
+ }
57273
+ }
57274
+ })
57275
+ );
57276
+ return { warnings, failed };
57277
+ }
57051
57278
  async function cleanupCriticalResources(context, projectId, _project) {
57052
57279
  const resources2 = await getProjectResources(context.get("db"), projectId);
57053
57280
  for (const resource of resources2) {
@@ -57118,8 +57345,10 @@ function parseDeploymentManifest(raw2) {
57118
57345
  }
57119
57346
  async function cleanupBestEffortResources(context, projectId, project) {
57120
57347
  const warnings = [];
57121
- const warn = /* @__PURE__ */ __name((msg) => (err) => {
57348
+ const failedJobs = [];
57349
+ const warn = /* @__PURE__ */ __name((msg, job) => (err) => {
57122
57350
  warnings.push(`${msg}: ${err instanceof Error ? err.message : String(err)}`);
57351
+ failedJobs.push(job);
57123
57352
  }, "warn");
57124
57353
  const cleanedCfIds = /* @__PURE__ */ new Set();
57125
57354
  const [projectDeployments, resources2, projectDomains] = await Promise.all([
@@ -57134,28 +57363,37 @@ async function cleanupBestEffortResources(context, projectId, project) {
57134
57363
  (deployment) => getProjectDeploymentWorkerName(project.slug, deployment.id)
57135
57364
  )
57136
57365
  ]);
57137
- await Promise.all(
57366
+ const workerResult = await runCleanupBatch(
57367
+ context.env,
57138
57368
  Array.from(
57139
57369
  workerNames,
57140
- (workerName) => fetch(
57141
- `https://api.cloudflare.com/client/v4/accounts/${context.env.CF_ACCOUNT_ID}/workers/dispatch/namespaces/${dispatchNamespace}/scripts/${workerName}`,
57142
- { method: "DELETE", headers: { Authorization: `Bearer ${context.env.CF_API_TOKEN}` } }
57143
- ).then(async (cfRes) => {
57144
- if (!cfRes.ok && cfRes.status !== 404) {
57145
- warnings.push(`Failed to delete worker ${workerName}: ${await cfRes.text()}`);
57146
- }
57147
- }).catch(warn(`Failed to delete worker ${workerName}`))
57370
+ (workerName) => cleanupJob(projectId, "worker", `${dispatchNamespace}:${workerName}`, {
57371
+ namespace: dispatchNamespace,
57372
+ name: workerName
57373
+ })
57148
57374
  )
57149
57375
  );
57150
- await Promise.all(
57376
+ warnings.push(...workerResult.warnings);
57377
+ failedJobs.push(...workerResult.failed);
57378
+ if (workerResult.failed.some((job) => job.payload.name === getLegacyProjectWorkerName(project.slug))) {
57379
+ throw new Error("Could not remove the legacy project Worker; retry project deletion.");
57380
+ }
57381
+ const bundleResult = await runCleanupBatch(
57382
+ context.env,
57151
57383
  projectDeployments.map(
57152
- (deployment) => deleteWorkerBundle(context.env.STATIC_ASSETS_R2, projectId, deployment.id).catch(
57153
- warn(`Failed to delete worker bundle for deployment ${deployment.id}`)
57154
- )
57384
+ (deployment) => cleanupJob(projectId, "worker-bundle", `${projectId}:${deployment.id}`, {
57385
+ projectId,
57386
+ deploymentId: deployment.id
57387
+ })
57155
57388
  )
57156
57389
  );
57390
+ warnings.push(...bundleResult.warnings);
57391
+ failedJobs.push(...bundleResult.failed);
57157
57392
  await deleteProjectAssets(context.env.STATIC_ASSETS_R2, projectId).catch(
57158
- warn(`Failed to delete static assets for project ${projectId}`)
57393
+ warn(
57394
+ `Failed to delete static assets for project ${projectId}`,
57395
+ cleanupJob(projectId, "project-assets", projectId, { projectId })
57396
+ )
57159
57397
  );
57160
57398
  const cleanup = [];
57161
57399
  for (const resource of resources2) {
@@ -57163,48 +57401,45 @@ async function cleanupBestEffortResources(context, projectId, project) {
57163
57401
  continue;
57164
57402
  }
57165
57403
  cleanedCfIds.add(resource.cf_id);
57166
- const del = deleteCloudflareResource(
57167
- context.env.CF_ACCOUNT_ID,
57168
- context.env.CF_API_TOKEN,
57169
- resource
57404
+ cleanup.push(
57405
+ ...cleanupJobsForResource(projectId, resource).filter((job) => job.kind === "cf-resource")
57170
57406
  );
57171
- cleanup.push(del.catch(warn(`Failed to delete ${resource.type} resource ${resource.cf_id}`)));
57172
57407
  }
57408
+ cleanup.push(cleanupJob(projectId, "cron", projectId, { projectId }));
57409
+ cleanup.push(cleanupJob(projectId, "routing-key", project.slug, { key: project.slug }));
57173
57410
  cleanup.push(
57174
- clearCronScheduler(context.env, projectId).catch(
57175
- warn(`Failed to clear cron scheduler for project ${projectId}`)
57176
- )
57177
- );
57178
- cleanup.push(
57179
- context.env.ROUTING.delete(project.slug).catch(warn("Failed to delete routing entry"))
57180
- );
57181
- cleanup.push(
57182
- context.env.ROUTING.delete(`ai-block:${projectId}`).catch(
57183
- warn("Failed to delete AI block entry")
57184
- )
57411
+ cleanupJob(projectId, "routing-key", `ai-block:${projectId}`, { key: `ai-block:${projectId}` })
57185
57412
  );
57186
57413
  for (const q of resources2.filter((r) => r.type === "queue")) {
57187
57414
  cleanup.push(
57188
- context.env.ROUTING.delete(`queue:${getProjectQueueName(projectId, q.binding_name)}`).catch(
57189
- warn("Failed to delete queue routing entry")
57190
- )
57415
+ ...cleanupJobsForResource(projectId, q).filter((job) => job.kind === "routing-key")
57191
57416
  );
57192
57417
  }
57193
57418
  for (const domain2 of projectDomains) {
57194
57419
  cleanup.push(
57195
- deleteCustomHostname(
57196
- context.env.CF_ZONE_ID,
57197
- context.env.CF_API_TOKEN,
57198
- domain2.cf_hostname_id
57199
- ).catch(warn(`Failed to delete custom hostname ${domain2.hostname}`))
57200
- );
57201
- cleanup.push(
57202
- context.env.ROUTING.delete(`domain:${domain2.hostname}`).catch(
57203
- warn("Failed to delete domain routing entry")
57204
- )
57420
+ cleanupJob(
57421
+ projectId,
57422
+ "custom-hostname",
57423
+ `${context.env.CF_ZONE_ID}:${domain2.cf_hostname_id}`,
57424
+ {
57425
+ zoneId: context.env.CF_ZONE_ID,
57426
+ hostnameId: domain2.cf_hostname_id
57427
+ }
57428
+ ),
57429
+ cleanupJob(projectId, "routing-key", `domain:${domain2.hostname}`, {
57430
+ key: `domain:${domain2.hostname}`
57431
+ })
57205
57432
  );
57206
57433
  }
57207
- await Promise.all(cleanup);
57434
+ const remaining = await runCleanupBatch(context.env, cleanup);
57435
+ warnings.push(...remaining.warnings);
57436
+ failedJobs.push(...remaining.failed);
57437
+ if (remaining.failed.some(
57438
+ (job) => job.kind === "routing-key" && (job.payload.key === project.slug || job.payload.key?.startsWith("domain:"))
57439
+ )) {
57440
+ throw new Error("Could not remove project routing; retry project deletion.");
57441
+ }
57442
+ await enqueueProjectCleanupJobs(context.get("db"), failedJobs);
57208
57443
  return { warnings, cleanedCfIds };
57209
57444
  }
57210
57445
  async function lockAndMarkDeleting(db, projectId, actorUserId, expectedOwnerUserId) {
@@ -57310,31 +57545,48 @@ async function detachProjectUsage(context, projectId, options) {
57310
57545
  }
57311
57546
  }
57312
57547
  async function cleanupRaceWindowResourceOrphans(context, projectId, deletedResources, cleanedCfIds) {
57313
- const warnings = [];
57314
- const warn = /* @__PURE__ */ __name((msg) => (err) => {
57315
- warnings.push(`${msg}: ${err instanceof Error ? err.message : String(err)}`);
57316
- }, "warn");
57317
57548
  const orphans = deletedResources.filter(
57318
57549
  (r) => r.type !== "container" && !cleanedCfIds.has(r.cf_id)
57319
57550
  );
57551
+ const jobs = orphans.flatMap((resource) => cleanupJobsForResource(projectId, resource));
57552
+ const result = await runCleanupBatch(context.env, jobs);
57553
+ const failedTargets = new Set(result.failed.map((job) => job.target));
57554
+ const completedTargets = jobs.filter((job) => !failedTargets.has(job.target)).map((job) => job.target);
57555
+ if (completedTargets.length > 0) {
57556
+ await context.get("db").delete(projectCleanupJobs).where(inArray(projectCleanupJobs.target, completedTargets)).catch((error63) => console.error("[project-cleanup] completed job delete failed:", error63));
57557
+ }
57558
+ return result.warnings;
57559
+ }
57560
+ async function reconcileProjectCleanupJobs(db, env2) {
57561
+ const due = await db.select().from(projectCleanupJobs).where(lte(projectCleanupJobs.next_attempt_at, sql`now()`)).orderBy(asc(projectCleanupJobs.created_at), asc(projectCleanupJobs.target)).limit(50);
57562
+ let next = 0;
57320
57563
  await Promise.all(
57321
- orphans.flatMap((r) => {
57322
- const tasks = [
57323
- deleteCloudflareResource(context.env.CF_ACCOUNT_ID, context.env.CF_API_TOKEN, r).catch(
57324
- warn(`Failed to delete race-window ${r.type} resource ${r.cf_id}`)
57325
- )
57326
- ];
57327
- if (r.type === "queue") {
57328
- tasks.push(
57329
- context.env.ROUTING.delete(
57330
- `queue:${getProjectQueueName(projectId, r.binding_name)}`
57331
- ).catch(warn(`Failed to delete race-window queue routing entry for ${r.cf_id}`))
57332
- );
57564
+ Array.from({ length: Math.min(5, due.length) }, async () => {
57565
+ while (next < due.length) {
57566
+ const job = due[next++];
57567
+ const claimed = await db.update(projectCleanupJobs).set({
57568
+ attempts: sql`${projectCleanupJobs.attempts} + 1`,
57569
+ next_attempt_at: new Date(Date.now() + 15 * 6e4).toISOString()
57570
+ }).where(
57571
+ and(
57572
+ eq(projectCleanupJobs.target, job.target),
57573
+ lte(projectCleanupJobs.next_attempt_at, sql`now()`)
57574
+ )
57575
+ ).returning({ target: projectCleanupJobs.target });
57576
+ if (claimed.length === 0) {
57577
+ continue;
57578
+ }
57579
+ try {
57580
+ await executeCleanupJob(env2, job);
57581
+ await db.delete(projectCleanupJobs).where(eq(projectCleanupJobs.target, job.target));
57582
+ } catch (error63) {
57583
+ const message2 = (error63 instanceof Error ? error63.message : String(error63)).replaceAll(env2.CF_API_TOKEN, "[redacted]").slice(0, 500);
57584
+ await db.update(projectCleanupJobs).set({ last_error: message2 }).where(eq(projectCleanupJobs.target, job.target));
57585
+ console.warn(`[project-cleanup] retry failed for ${job.target}: ${message2}`);
57586
+ }
57333
57587
  }
57334
- return tasks;
57335
57588
  })
57336
57589
  );
57337
- return warnings;
57338
57590
  }
57339
57591
  async function teardownProject(context, projectId, project, options = {}) {
57340
57592
  const deletion = await lockAndMarkDeleting(
@@ -57364,7 +57616,8 @@ async function teardownProject(context, projectId, project, options = {}) {
57364
57616
  await cleanupProjectEmailDestinations(context, projectId);
57365
57617
  const { cancelledBuilds, deletedResources } = await deleteProjectCascade(
57366
57618
  context.get("db"),
57367
- projectId
57619
+ projectId,
57620
+ cleanedCfIds
57368
57621
  );
57369
57622
  await cancelContainerBuildDos(context.env, cancelledBuilds, "project deleted");
57370
57623
  const orphanWarnings = await cleanupRaceWindowResourceOrphans(
@@ -57452,6 +57705,11 @@ var init_cleanup = __esm({
57452
57705
  init_email_destination_cleanup();
57453
57706
  init_sandbox_controller();
57454
57707
  __name(deleteCloudflareResource, "deleteCloudflareResource");
57708
+ __name(cleanupJob, "cleanupJob");
57709
+ __name(jobValue, "jobValue");
57710
+ __name(ignoreMissingProviderObject, "ignoreMissingProviderObject");
57711
+ __name(executeCleanupJob, "executeCleanupJob");
57712
+ __name(runCleanupBatch, "runCleanupBatch");
57455
57713
  __name(cleanupCriticalResources, "cleanupCriticalResources");
57456
57714
  __name(sweepPerDeployContainerOrphans, "sweepPerDeployContainerOrphans");
57457
57715
  __name(parseDeploymentManifest, "parseDeploymentManifest");
@@ -57468,6 +57726,7 @@ var init_cleanup = __esm({
57468
57726
  }
57469
57727
  };
57470
57728
  __name(cleanupRaceWindowResourceOrphans, "cleanupRaceWindowResourceOrphans");
57729
+ __name(reconcileProjectCleanupJobs, "reconcileProjectCleanupJobs");
57471
57730
  __name(teardownProject, "teardownProject");
57472
57731
  __name(cleanupProjectEmailDestinations, "cleanupProjectEmailDestinations");
57473
57732
  __name(cleanupProjectEmailAssignments, "cleanupProjectEmailAssignments");
@@ -69742,7 +70001,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
69742
70001
  return [workInProgressHook.memoizedState, reducer];
69743
70002
  }
69744
70003
  __name(useReducer, "useReducer");
69745
- function useMemo39(nextCreate, deps) {
70004
+ function useMemo36(nextCreate, deps) {
69746
70005
  currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
69747
70006
  workInProgressHook = createWorkInProgressHook();
69748
70007
  deps = void 0 === deps ? null : deps;
@@ -69766,7 +70025,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
69766
70025
  workInProgressHook.memoizedState = [nextCreate, deps];
69767
70026
  return nextCreate;
69768
70027
  }
69769
- __name(useMemo39, "useMemo");
70028
+ __name(useMemo36, "useMemo");
69770
70029
  function dispatchAction(componentIdentity, queue, action2) {
69771
70030
  if (25 <= numberOfReRenders)
69772
70031
  throw Error(
@@ -69867,7 +70126,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
69867
70126
  resolveCurrentlyRenderingComponent();
69868
70127
  return context._currentValue;
69869
70128
  }, "useContext"),
69870
- useMemo: useMemo39,
70129
+ useMemo: useMemo36,
69871
70130
  useReducer,
69872
70131
  useRef: /* @__PURE__ */ __name(function(initialValue) {
69873
70132
  currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
@@ -69881,7 +70140,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
69881
70140
  useInsertionEffect: noop,
69882
70141
  useLayoutEffect: noop,
69883
70142
  useCallback: /* @__PURE__ */ __name(function(callback, deps) {
69884
- return useMemo39(function() {
70143
+ return useMemo36(function() {
69885
70144
  return callback;
69886
70145
  }, deps);
69887
70146
  }, "useCallback"),
@@ -75507,7 +75766,7 @@ var require_react_dom_server_legacy_browser_production = __commonJS({
75507
75766
  return [workInProgressHook.memoizedState, reducer];
75508
75767
  }
75509
75768
  __name(useReducer, "useReducer");
75510
- function useMemo39(nextCreate, deps) {
75769
+ function useMemo36(nextCreate, deps) {
75511
75770
  currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
75512
75771
  workInProgressHook = createWorkInProgressHook();
75513
75772
  deps = void 0 === deps ? null : deps;
@@ -75531,7 +75790,7 @@ var require_react_dom_server_legacy_browser_production = __commonJS({
75531
75790
  workInProgressHook.memoizedState = [nextCreate, deps];
75532
75791
  return nextCreate;
75533
75792
  }
75534
- __name(useMemo39, "useMemo");
75793
+ __name(useMemo36, "useMemo");
75535
75794
  function dispatchAction(componentIdentity, queue, action2) {
75536
75795
  if (25 <= numberOfReRenders) throw Error(formatProdErrorMessage(301));
75537
75796
  if (componentIdentity === currentlyRenderingComponent)
@@ -75628,7 +75887,7 @@ var require_react_dom_server_legacy_browser_production = __commonJS({
75628
75887
  resolveCurrentlyRenderingComponent();
75629
75888
  return context._currentValue2;
75630
75889
  }, "useContext"),
75631
- useMemo: useMemo39,
75890
+ useMemo: useMemo36,
75632
75891
  useReducer,
75633
75892
  useRef: /* @__PURE__ */ __name(function(initialValue) {
75634
75893
  currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
@@ -75642,7 +75901,7 @@ var require_react_dom_server_legacy_browser_production = __commonJS({
75642
75901
  useInsertionEffect: noop,
75643
75902
  useLayoutEffect: noop,
75644
75903
  useCallback: /* @__PURE__ */ __name(function(callback, deps) {
75645
- return useMemo39(function() {
75904
+ return useMemo36(function() {
75646
75905
  return callback;
75647
75906
  }, deps);
75648
75907
  }, "useCallback"),
@@ -78469,13 +78728,130 @@ var init_context2 = __esm({
78469
78728
  });
78470
78729
 
78471
78730
  // ../../home/runner/work/void/void/packages/react/src/runtime/deferred-shell.ts
78472
- var DEFERRED_SHELL_END_ID, DEFERRED_SHELL_END_MARKER;
78731
+ var DEFERRED_SHELL_END_ID, DEFERRED_SHELL_END_MARKER, DEFERRED_SHELL_CANCEL_REASON;
78473
78732
  var init_deferred_shell = __esm({
78474
78733
  "../../home/runner/work/void/void/packages/react/src/runtime/deferred-shell.ts"() {
78475
78734
  "use strict";
78476
78735
  init_performance2();
78477
78736
  DEFERRED_SHELL_END_ID = "__VOID_DEFERRED_SHELL_END__";
78478
78737
  DEFERRED_SHELL_END_MARKER = `<template id="${DEFERRED_SHELL_END_ID}"></template>`;
78738
+ DEFERRED_SHELL_CANCEL_REASON = "Void deferred shell complete";
78739
+ }
78740
+ });
78741
+
78742
+ // ../../home/runner/work/void/void/packages/react/src/runtime/page-streams.ts
78743
+ function concatStreams(streams) {
78744
+ let index2 = 0;
78745
+ let reader;
78746
+ let cancelled = false;
78747
+ const cancelRemaining = /* @__PURE__ */ __name(async (reason) => {
78748
+ const cancellations = [];
78749
+ if (reader) {
78750
+ cancellations.push(reader.cancel(reason));
78751
+ }
78752
+ for (let remaining = index2 + (reader ? 1 : 0); remaining < streams.length; remaining++) {
78753
+ cancellations.push(streams[remaining].cancel(reason));
78754
+ }
78755
+ await Promise.allSettled(cancellations);
78756
+ }, "cancelRemaining");
78757
+ return new ReadableStream(
78758
+ {
78759
+ async pull(controller) {
78760
+ try {
78761
+ while (index2 < streams.length) {
78762
+ reader ??= streams[index2].getReader();
78763
+ const { done, value } = await reader.read();
78764
+ if (cancelled) {
78765
+ return;
78766
+ }
78767
+ if (done) {
78768
+ reader.releaseLock();
78769
+ reader = void 0;
78770
+ index2++;
78771
+ continue;
78772
+ }
78773
+ controller.enqueue(value);
78774
+ return;
78775
+ }
78776
+ controller.close();
78777
+ } catch (error63) {
78778
+ if (!cancelled) {
78779
+ controller.error(error63);
78780
+ await cancelRemaining(error63);
78781
+ }
78782
+ }
78783
+ },
78784
+ async cancel(reason) {
78785
+ cancelled = true;
78786
+ await cancelRemaining(reason);
78787
+ }
78788
+ },
78789
+ { highWaterMark: 0 }
78790
+ );
78791
+ }
78792
+ function readDeferredShell(stream) {
78793
+ const reader = stream.getReader();
78794
+ const decoder5 = new TextDecoder();
78795
+ const encoder12 = new TextEncoder();
78796
+ const keepLength = DEFERRED_SHELL_END_MARKER.length - 1;
78797
+ let buffer = "";
78798
+ let cancelled = false;
78799
+ return new ReadableStream(
78800
+ {
78801
+ async pull(controller) {
78802
+ try {
78803
+ while (true) {
78804
+ const { done, value } = await reader.read();
78805
+ if (cancelled) {
78806
+ return;
78807
+ }
78808
+ buffer += done ? decoder5.decode() : decoder5.decode(value, { stream: true });
78809
+ const markerIndex = buffer.indexOf(DEFERRED_SHELL_END_MARKER);
78810
+ if (markerIndex !== -1) {
78811
+ const shell = buffer.slice(0, markerIndex + DEFERRED_SHELL_END_MARKER.length);
78812
+ await reader.cancel(DEFERRED_SHELL_CANCEL_REASON);
78813
+ if (!cancelled) {
78814
+ controller.enqueue(encoder12.encode(shell));
78815
+ controller.close();
78816
+ }
78817
+ return;
78818
+ }
78819
+ if (done) {
78820
+ if (buffer) {
78821
+ controller.enqueue(encoder12.encode(buffer));
78822
+ }
78823
+ controller.close();
78824
+ return;
78825
+ }
78826
+ if (buffer.length > keepLength) {
78827
+ controller.enqueue(encoder12.encode(buffer.slice(0, -keepLength)));
78828
+ buffer = buffer.slice(-keepLength);
78829
+ return;
78830
+ }
78831
+ }
78832
+ } catch (error63) {
78833
+ if (!cancelled) {
78834
+ controller.error(error63);
78835
+ await reader.cancel(error63).catch(() => {
78836
+ });
78837
+ }
78838
+ }
78839
+ },
78840
+ async cancel(reason) {
78841
+ cancelled = true;
78842
+ await reader.cancel(reason);
78843
+ }
78844
+ },
78845
+ { highWaterMark: 0 }
78846
+ );
78847
+ }
78848
+ var init_page_streams = __esm({
78849
+ "../../home/runner/work/void/void/packages/react/src/runtime/page-streams.ts"() {
78850
+ "use strict";
78851
+ init_performance2();
78852
+ init_deferred_shell();
78853
+ __name(concatStreams, "concatStreams");
78854
+ __name(readDeferredShell, "readDeferredShell");
78479
78855
  }
78480
78856
  });
78481
78857
 
@@ -78503,67 +78879,6 @@ function textStream(text2) {
78503
78879
  }
78504
78880
  });
78505
78881
  }
78506
- function concatStreams(streams) {
78507
- return new ReadableStream({
78508
- async start(controller) {
78509
- try {
78510
- for (const stream of streams) {
78511
- const reader = stream.getReader();
78512
- while (true) {
78513
- const { done, value } = await reader.read();
78514
- if (done) {
78515
- break;
78516
- }
78517
- controller.enqueue(value);
78518
- }
78519
- }
78520
- controller.close();
78521
- } catch (error63) {
78522
- controller.error(error63);
78523
- }
78524
- }
78525
- });
78526
- }
78527
- function readDeferredShell(stream) {
78528
- const encoder12 = new TextEncoder();
78529
- return new ReadableStream({
78530
- async start(controller) {
78531
- const reader = stream.getReader();
78532
- const decoder5 = new TextDecoder();
78533
- let buffer = "";
78534
- const keepLength = DEFERRED_SHELL_END_MARKER.length - 1;
78535
- const flush = /* @__PURE__ */ __name((text2) => {
78536
- if (text2) {
78537
- controller.enqueue(encoder12.encode(text2));
78538
- }
78539
- }, "flush");
78540
- try {
78541
- while (true) {
78542
- const { done, value } = await reader.read();
78543
- if (done) {
78544
- buffer += decoder5.decode();
78545
- flush(buffer);
78546
- break;
78547
- }
78548
- buffer += decoder5.decode(value, { stream: true });
78549
- const markerIndex = buffer.indexOf(DEFERRED_SHELL_END_MARKER);
78550
- if (markerIndex !== -1) {
78551
- flush(buffer.slice(0, markerIndex + DEFERRED_SHELL_END_MARKER.length));
78552
- await reader.cancel(DEFERRED_SHELL_CANCEL_REASON);
78553
- break;
78554
- }
78555
- if (buffer.length > keepLength) {
78556
- flush(buffer.slice(0, -keepLength));
78557
- buffer = buffer.slice(-keepLength);
78558
- }
78559
- }
78560
- controller.close();
78561
- } catch (error63) {
78562
- controller.error(error63);
78563
- }
78564
- }
78565
- });
78566
- }
78567
78882
  async function renderReactPage({
78568
78883
  pageObj,
78569
78884
  assetTags,
@@ -78635,7 +78950,7 @@ async function renderReactPage({
78635
78950
  </html>`)
78636
78951
  ]);
78637
78952
  }
78638
- var import_react2, import_server, DEFERRED_SHELL_CANCEL_REASON;
78953
+ var import_react2, import_server;
78639
78954
  var init_pages_server = __esm({
78640
78955
  "../../home/runner/work/void/void/packages/react/src/runtime/pages-server.ts"() {
78641
78956
  init_performance2();
@@ -78646,12 +78961,10 @@ var init_pages_server = __esm({
78646
78961
  init_serialize();
78647
78962
  init_context2();
78648
78963
  init_deferred_shell();
78649
- DEFERRED_SHELL_CANCEL_REASON = "Void deferred shell complete";
78964
+ init_page_streams();
78650
78965
  __name(createPendingDeferred, "createPendingDeferred");
78651
78966
  __name(propsForReactRender, "propsForReactRender");
78652
78967
  __name(textStream, "textStream");
78653
- __name(concatStreams, "concatStreams");
78654
- __name(readDeferredShell, "readDeferredShell");
78655
78968
  __name(renderReactPage, "renderReactPage");
78656
78969
  }
78657
78970
  });
@@ -121169,38 +121482,28 @@ var init_Typeahead2 = __esm({
121169
121482
  function SearchTypeahead({
121170
121483
  label,
121171
121484
  onChange,
121172
- suggestions,
121485
+ onSelect,
121486
+ onClear,
121487
+ suggestions = noSuggestions,
121488
+ remoteSearch,
121173
121489
  initialValue = "",
121490
+ initialSelection,
121174
121491
  width = 320
121175
121492
  }) {
121176
121493
  const items = (0, import_react148.useMemo)(() => {
121177
- const byId = /* @__PURE__ */ new Map();
121178
- for (const suggestion of suggestions) {
121179
- if (!suggestion) {
121180
- continue;
121181
- }
121182
- const item = typeof suggestion === "string" ? { id: suggestion, label: suggestion, auxiliaryData: { keywords: [] } } : {
121183
- id: suggestion.id,
121184
- label: suggestion.label,
121185
- auxiliaryData: {
121186
- keywords: suggestion.keywords?.filter((value) => Boolean(value)) ?? [],
121187
- description: suggestion.description
121188
- }
121189
- };
121190
- if (item.id && item.label) {
121191
- byId.set(item.id, item);
121192
- }
121193
- }
121194
- return [...byId.values()];
121494
+ return searchItems(suggestions);
121195
121495
  }, [suggestions]);
121196
121496
  const source = (0, import_react148.useMemo)(
121197
- () => createStaticSource(items, {
121497
+ () => remoteSearch ? {
121498
+ bootstrap: /* @__PURE__ */ __name(() => [], "bootstrap"),
121499
+ search: /* @__PURE__ */ __name(async (query) => searchItems(await remoteSearch(query)), "search")
121500
+ } : createStaticSource(items, {
121198
121501
  keywords: /* @__PURE__ */ __name((item) => item.auxiliaryData?.keywords ?? [], "keywords")
121199
121502
  }),
121200
- [items]
121503
+ [items, remoteSearch]
121201
121504
  );
121202
121505
  const [selected, setSelected] = (0, import_react148.useState)(
121203
- () => initialValue ? items.find((item) => item.id === initialValue) ?? {
121506
+ () => initialValue ? items.find((item) => item.id === initialValue) ?? (initialSelection?.id === initialValue ? searchItems([initialSelection])[0] : void 0) ?? {
121204
121507
  id: initialValue,
121205
121508
  label: initialValue
121206
121509
  } : null
@@ -121217,19 +121520,46 @@ function SearchTypeahead({
121217
121520
  onChange: (item) => {
121218
121521
  setSelected(item);
121219
121522
  onChange(item?.id ?? "");
121523
+ if (item) {
121524
+ onSelect?.(item.id);
121525
+ } else {
121526
+ onClear?.();
121527
+ }
121220
121528
  },
121221
121529
  onChangeQuery: (query) => {
121222
121530
  setSelected(null);
121223
121531
  onChange(query);
121224
121532
  },
121225
- hasEntriesOnFocus: true,
121226
- debounceMs: 0,
121533
+ hasEntriesOnFocus: !remoteSearch,
121534
+ hasClear: true,
121535
+ minQueryLength: remoteSearch ? 2 : 1,
121536
+ debounceMs: remoteSearch ? 200 : 0,
121227
121537
  width,
121228
- emptySearchResultsText: "No suggestions. Search for any text."
121538
+ emptySearchResultsText: remoteSearch ? "No matching results." : "No suggestions. Search for any text."
121229
121539
  }
121230
121540
  );
121231
121541
  }
121232
- var import_react148, import_jsx_runtime103;
121542
+ function searchItems(suggestions) {
121543
+ const byId = /* @__PURE__ */ new Map();
121544
+ for (const suggestion of suggestions) {
121545
+ if (!suggestion) {
121546
+ continue;
121547
+ }
121548
+ const item = typeof suggestion === "string" ? { id: suggestion, label: suggestion, auxiliaryData: { keywords: [] } } : {
121549
+ id: suggestion.id,
121550
+ label: suggestion.label,
121551
+ auxiliaryData: {
121552
+ keywords: suggestion.keywords?.filter((value) => Boolean(value)) ?? [],
121553
+ description: suggestion.description
121554
+ }
121555
+ };
121556
+ if (item.id && item.label) {
121557
+ byId.set(item.id, item);
121558
+ }
121559
+ }
121560
+ return [...byId.values()];
121561
+ }
121562
+ var import_react148, import_jsx_runtime103, noSuggestions;
121233
121563
  var init_SearchTypeahead = __esm({
121234
121564
  "../../home/runner/work/void/void/platform/packages/api/src/admin/react/views/SearchTypeahead.tsx"() {
121235
121565
  "use strict";
@@ -121237,7 +121567,9 @@ var init_SearchTypeahead = __esm({
121237
121567
  init_Typeahead2();
121238
121568
  import_react148 = __toESM(require_react(), 1);
121239
121569
  import_jsx_runtime103 = __toESM(require_jsx_runtime(), 1);
121570
+ noSuggestions = [];
121240
121571
  __name(SearchTypeahead, "SearchTypeahead");
121572
+ __name(searchItems, "searchItems");
121241
121573
  }
121242
121574
  });
121243
121575
 
@@ -121631,13 +121963,61 @@ var init_Pagination = __esm({
121631
121963
  // ../../home/runner/work/void/void/platform/packages/api/src/admin/react/views/SearchForm.tsx
121632
121964
  function SearchForm({
121633
121965
  action: action2,
121966
+ kind,
121634
121967
  label,
121635
121968
  value,
121636
121969
  onChange,
121637
- suggestions,
121638
121970
  initialValue,
121639
- showClear
121971
+ initialSelection
121640
121972
  }) {
121973
+ const fate = useFateClient();
121974
+ const router = useRouter();
121975
+ const remoteSearch = (0, import_react151.useCallback)(
121976
+ async (query) => {
121977
+ const path = `${action2}?search=${encodeURIComponent(query)}`;
121978
+ if (kind === "users") {
121979
+ const { usersPage: ref2 } = await fate.request(usersPageRequest(path), {
121980
+ mode: "network-only"
121981
+ });
121982
+ if (!ref2) {
121983
+ return [];
121984
+ }
121985
+ const { data: data2 } = await fate.readView(UsersPageView, ref2);
121986
+ return data2.users.map((user) => ({
121987
+ id: user.id,
121988
+ label: user.login,
121989
+ description: [user.email, user.id].filter(Boolean).join(" \xB7 ")
121990
+ }));
121991
+ }
121992
+ if (kind === "projects") {
121993
+ const { projectsPage: ref2 } = await fate.request(projectsPageRequest(path), {
121994
+ mode: "network-only"
121995
+ });
121996
+ if (!ref2) {
121997
+ return [];
121998
+ }
121999
+ const { data: data2 } = await fate.readView(ProjectsPageView, ref2);
122000
+ return data2.projects.map((project) => ({
122001
+ id: project.id,
122002
+ label: project.slug,
122003
+ description: project.owner_login ?? void 0
122004
+ }));
122005
+ }
122006
+ const { deploymentsPage: ref } = await fate.request(deploymentsPageRequest(path), {
122007
+ mode: "network-only"
122008
+ });
122009
+ if (!ref) {
122010
+ return [];
122011
+ }
122012
+ const { data } = await fate.readView(DeploymentsPageView, ref);
122013
+ return data.deployments.map((deployment) => ({
122014
+ id: deployment.id,
122015
+ label: deployment.id,
122016
+ description: [deployment.project_slug, deployment.status].filter(Boolean).join(" \xB7 ")
122017
+ }));
122018
+ },
122019
+ [action2, fate, kind]
122020
+ );
121641
122021
  return /* @__PURE__ */ (0, import_jsx_runtime107.jsxs)("form", { method: "get", action: action2, className: "void-admin-search", children: [
121642
122022
  /* @__PURE__ */ (0, import_jsx_runtime107.jsx)("input", { type: "hidden", name: "search", value }),
121643
122023
  /* @__PURE__ */ (0, import_jsx_runtime107.jsx)(
@@ -121645,22 +122025,31 @@ function SearchForm({
121645
122025
  {
121646
122026
  label,
121647
122027
  onChange,
121648
- suggestions,
121649
- initialValue
122028
+ onSelect: (id2) => void router.visit(`${action2}/${encodeURIComponent(id2)}`),
122029
+ onClear: () => {
122030
+ if (initialValue) {
122031
+ void router.visit(action2);
122032
+ }
122033
+ },
122034
+ remoteSearch,
122035
+ initialValue,
122036
+ initialSelection
121650
122037
  },
121651
122038
  initialValue ?? ""
121652
122039
  ),
121653
- /* @__PURE__ */ (0, import_jsx_runtime107.jsx)(Button, { type: "submit", label: "Search" }),
121654
- showClear && /* @__PURE__ */ (0, import_jsx_runtime107.jsx)(Link, { href: action2, children: "Clear" })
122040
+ /* @__PURE__ */ (0, import_jsx_runtime107.jsx)(Button, { type: "submit", label: "Search" })
121655
122041
  ] });
121656
122042
  }
121657
- var import_jsx_runtime107;
122043
+ var import_react151, import_jsx_runtime107;
121658
122044
  var init_SearchForm = __esm({
121659
122045
  "../../home/runner/work/void/void/platform/packages/api/src/admin/react/views/SearchForm.tsx"() {
121660
122046
  "use strict";
121661
122047
  init_performance2();
121662
122048
  init_Button2();
121663
- init_Link2();
122049
+ init_runtime();
122050
+ import_react151 = __toESM(require_react(), 1);
122051
+ init_lib2();
122052
+ init_views();
121664
122053
  init_SearchTypeahead();
121665
122054
  import_jsx_runtime107 = __toESM(require_jsx_runtime(), 1);
121666
122055
  __name(SearchForm, "SearchForm");
@@ -121677,59 +122066,37 @@ function DeploymentsListPage({ path, platformName, username }) {
121677
122066
  throw new Error("Deployments page data is unavailable.");
121678
122067
  }
121679
122068
  const { deployments: deployments2, search, page, totalPages, tz } = pageData;
121680
- const [filter, setFilter] = (0, import_react150.useState)(search ?? "");
121681
- (0, import_react150.useEffect)(() => setFilter(search ?? ""), [search]);
121682
- const visibleDeployments = deployments2.filter(
121683
- (deployment) => [
121684
- deployment.id,
121685
- deployment.project_slug,
121686
- deployment.status,
121687
- deployment.source,
121688
- deployment.cli_version,
121689
- deployment.commit,
121690
- deployment.error_message
121691
- ].some((value) => value?.toLowerCase().includes(filter.toLowerCase()))
121692
- );
121693
- const suggestions = (0, import_react150.useMemo)(
121694
- () => deployments2.map((deployment) => ({
121695
- id: deployment.id,
121696
- label: deployment.id,
121697
- keywords: [
121698
- deployment.project_slug,
121699
- deployment.status,
121700
- deployment.source,
121701
- deployment.cli_version,
121702
- deployment.commit,
121703
- deployment.error_message
121704
- ],
121705
- description: [deployment.project_slug, deployment.status].filter(Boolean).join(" \xB7 ")
121706
- })),
121707
- [deployments2]
121708
- );
122069
+ const [filter, setFilter] = (0, import_react152.useState)(search ?? "");
122070
+ (0, import_react152.useEffect)(() => setFilter(search ?? ""), [search]);
122071
+ const selectedDeployment = deployments2.find((deployment) => deployment.id === search);
121709
122072
  return /* @__PURE__ */ jsxs(Layout2, { platformName, title: "Deployments", username, children: [
121710
122073
  /* @__PURE__ */ jsx4("h1", { children: "Deployments" }),
121711
122074
  /* @__PURE__ */ jsx4(
121712
122075
  SearchForm,
121713
122076
  {
121714
122077
  action: "/admin/deployments",
122078
+ kind: "deployments",
121715
122079
  label: "Search deployments",
121716
122080
  value: filter,
121717
122081
  onChange: setFilter,
121718
- suggestions,
121719
122082
  initialValue: search,
121720
- showClear: Boolean(search)
122083
+ initialSelection: selectedDeployment ? {
122084
+ id: selectedDeployment.id,
122085
+ label: selectedDeployment.id,
122086
+ description: [selectedDeployment.project_slug, selectedDeployment.status].filter(Boolean).join(" \xB7 ")
122087
+ } : void 0
121721
122088
  }
121722
122089
  ),
121723
- /* @__PURE__ */ jsx4(DeploymentTable, { deployments: visibleDeployments, showProject: true, tz }),
122090
+ /* @__PURE__ */ jsx4(DeploymentTable, { deployments: deployments2, showProject: true, tz }),
121724
122091
  page != null && totalPages != null && /* @__PURE__ */ jsx4(Pagination, { page, totalPages, baseUrl: "/admin/deployments" })
121725
122092
  ] });
121726
122093
  }
121727
- var import_react150;
122094
+ var import_react152;
121728
122095
  var init_DeploymentsListPage = __esm({
121729
122096
  "../../home/runner/work/void/void/platform/packages/api/src/admin/react/views/DeploymentsListPage.tsx"() {
121730
122097
  "use strict";
121731
122098
  init_performance2();
121732
- import_react150 = __toESM(require_react(), 1);
122099
+ import_react152 = __toESM(require_react(), 1);
121733
122100
  init_lib2();
121734
122101
  init_views();
121735
122102
  init_DeploymentTable();
@@ -122099,13 +122466,13 @@ function RadioList({
122099
122466
  htmlName,
122100
122467
  children
122101
122468
  }) {
122102
- const autoName = (0, import_react151.useId)();
122469
+ const autoName = (0, import_react153.useId)();
122103
122470
  const name = htmlName ?? autoName;
122104
- const inputID = (0, import_react151.useId)();
122105
- const labelID = (0, import_react151.useId)();
122106
- const descriptionID = (0, import_react151.useId)();
122107
- const statusMessageID = (0, import_react151.useId)();
122108
- const groupRef = (0, import_react151.useRef)(null);
122471
+ const inputID = (0, import_react153.useId)();
122472
+ const labelID = (0, import_react153.useId)();
122473
+ const descriptionID = (0, import_react153.useId)();
122474
+ const statusMessageID = (0, import_react153.useId)();
122475
+ const groupRef = (0, import_react153.useRef)(null);
122109
122476
  const isEffectivelyRequired = useResolvedRequired({
122110
122477
  isRequired,
122111
122478
  isOptional
@@ -122118,7 +122485,7 @@ function RadioList({
122118
122485
  focusTrigger: "always",
122119
122486
  isEnabled: showsDisabledMessage
122120
122487
  });
122121
- const contextValue = (0, import_react151.useMemo)(() => ({
122488
+ const contextValue = (0, import_react153.useMemo)(() => ({
122122
122489
  name,
122123
122490
  value,
122124
122491
  onChange,
@@ -122128,7 +122495,7 @@ function RadioList({
122128
122495
  size,
122129
122496
  status: status2
122130
122497
  }), [name, value, onChange, isDisabled, showsDisabledMessage, isRequired, size, status2]);
122131
- const handleFocus = (0, import_react151.useCallback)((e) => {
122498
+ const handleFocus = (0, import_react153.useCallback)((e) => {
122132
122499
  if (value !== "") {
122133
122500
  return;
122134
122501
  }
@@ -122212,12 +122579,12 @@ function RadioList({
122212
122579
  }), showsDisabledMessage && disabledMessageTooltip.renderTooltip(disabledMessage)]
122213
122580
  });
122214
122581
  }
122215
- var import_react151, import_jsx_runtime111, RadioListContext;
122582
+ var import_react153, import_jsx_runtime111, RadioListContext;
122216
122583
  var init_RadioList = __esm({
122217
122584
  "../../home/runner/work/void/void/node_modules/.pnpm/@astryxdesign+core@0.6.3_@stylexjs+stylex@0.19.1_react-dom@19.3.0_react@19.3.0__react@19.3.0/node_modules/@astryxdesign/core/dist/RadioList/RadioList.js"() {
122218
122585
  "use client";
122219
122586
  init_performance2();
122220
- import_react151 = __toESM(require_react(), 1);
122587
+ import_react153 = __toESM(require_react(), 1);
122221
122588
  init_tokens_stylex();
122222
122589
  init_tokens_stylex();
122223
122590
  init_Field();
@@ -122226,7 +122593,7 @@ var init_RadioList = __esm({
122226
122593
  init_utils7();
122227
122594
  init_themeProps();
122228
122595
  import_jsx_runtime111 = __toESM(require_jsx_runtime(), 1);
122229
- RadioListContext = /* @__PURE__ */ (0, import_react151.createContext)(null);
122596
+ RadioListContext = /* @__PURE__ */ (0, import_react153.createContext)(null);
122230
122597
  RadioListContext.displayName = "RadioListContext";
122231
122598
  __name(RadioList, "RadioList");
122232
122599
  RadioList.displayName = "RadioList";
@@ -122249,24 +122616,24 @@ function RadioListItem({
122249
122616
  onClick,
122250
122617
  ...rest
122251
122618
  }) {
122252
- const context = (0, import_react152.use)(RadioListContext);
122619
+ const context = (0, import_react154.use)(RadioListContext);
122253
122620
  if (!context) {
122254
122621
  throw new Error("RadioListItem must be used within an RadioList");
122255
122622
  }
122256
- const id2 = (0, import_react152.useId)();
122257
- const labelID = (0, import_react152.useId)();
122258
- const descriptionID = (0, import_react152.useId)();
122623
+ const id2 = (0, import_react154.useId)();
122624
+ const labelID = (0, import_react154.useId)();
122625
+ const descriptionID = (0, import_react154.useId)();
122259
122626
  const isDisabled = context.isDisabled || isItemDisabled;
122260
122627
  const keepsFocusableForMessage = context.hasDisabledMessage && !isItemDisabled;
122261
122628
  const isChecked = context.value === value;
122262
122629
  const hasDescription = isRenderable(description);
122263
122630
  const size = context.size;
122264
122631
  const RadioControl = useIndicator("radio");
122265
- const indicatorRef = (0, import_react152.useRef)(null);
122632
+ const indicatorRef = (0, import_react154.useRef)(null);
122266
122633
  const {
122267
122634
  focusProps
122268
122635
  } = useIndicatorFocusRing(indicatorRef, isDisabled);
122269
- const radioRef = (0, import_react152.useRef)(null);
122636
+ const radioRef = (0, import_react154.useRef)(null);
122270
122637
  const radioCircle = /* @__PURE__ */ (0, import_jsx_runtime112.jsxs)("div", {
122271
122638
  ...props(styles56.radioWrapper, wrapperSizeStyles3[size], !isDisabled && styles56.indicatorPressOverlay),
122272
122639
  ...focusProps,
@@ -122356,12 +122723,12 @@ function RadioListItem({
122356
122723
  ...rest
122357
122724
  });
122358
122725
  }
122359
- var import_react152, import_jsx_runtime112, styles56, wrapperSizeStyles3, rowStyles;
122726
+ var import_react154, import_jsx_runtime112, styles56, wrapperSizeStyles3, rowStyles;
122360
122727
  var init_RadioListItem = __esm({
122361
122728
  "../../home/runner/work/void/void/node_modules/.pnpm/@astryxdesign+core@0.6.3_@stylexjs+stylex@0.19.1_react-dom@19.3.0_react@19.3.0__react@19.3.0/node_modules/@astryxdesign/core/dist/RadioList/RadioListItem.js"() {
122362
122729
  "use client";
122363
122730
  init_performance2();
122364
- import_react152 = __toESM(require_react(), 1);
122731
+ import_react154 = __toESM(require_react(), 1);
122365
122732
  init_stylex();
122366
122733
  init_RadioList();
122367
122734
  init_tokens_stylex();
@@ -122478,29 +122845,29 @@ function EnabledEmailSettingsPage({ path, platformName, username }) {
122478
122845
  throw new Error("Email settings are unavailable.");
122479
122846
  }
122480
122847
  const { policy, management, senderDomain } = pageData;
122481
- const [mode, setMode] = (0, import_react153.useState)(policy.mode);
122482
- const [domainManagement, setDomainManagement] = (0, import_react153.useState)(management);
122848
+ const [mode, setMode] = (0, import_react155.useState)(policy.mode);
122849
+ const [domainManagement, setDomainManagement] = (0, import_react155.useState)(management);
122483
122850
  return /* @__PURE__ */ jsxs(Layout2, { platformName, title: "Email", username, children: [
122484
122851
  /* @__PURE__ */ jsx4("h1", { children: "Email" }),
122485
122852
  /* @__PURE__ */ jsx4("p", { style: META2, children: "Installation-wide settings. Per-project usage, caps and domains live on each project's email page (Projects \u2192 project \u2192 Email)." }),
122486
122853
  /* @__PURE__ */ jsx4("h2", { children: "Recipient policy" }),
122487
122854
  /* @__PURE__ */ jsxs("p", { children: [
122488
- "Who mail from the shared sender",
122855
+ "Choose which recipients can receive email sent from the shared sender",
122489
122856
  senderDomain ? /* @__PURE__ */ jsxs(import_jsx_runtime18.Fragment, { children: [
122490
122857
  " ",
122491
122858
  "(",
122492
122859
  /* @__PURE__ */ jsx4("code", { children: `<slug>+tag@${senderDomain}` }),
122493
122860
  ")"
122494
122861
  ] }) : null,
122495
- " ",
122496
- "may reach. Custom-domain sends are not affected."
122862
+ ". This does not affect email sent from custom domains."
122497
122863
  ] }),
122498
- /* @__PURE__ */ jsxs("form", { method: "post", action: "/admin/email/policy", style: "margin-bottom: 8px", children: [
122864
+ /* @__PURE__ */ jsxs("form", { method: "post", action: "/admin/email/policy", className: "void-admin-email-choice-form", children: [
122499
122865
  /* @__PURE__ */ jsxs(
122500
122866
  RadioList,
122501
122867
  {
122502
122868
  label: "Recipient policy",
122503
122869
  isLabelHidden: true,
122870
+ size: "sm",
122504
122871
  htmlName: "mode",
122505
122872
  value: mode,
122506
122873
  onChange: (value) => setMode(value),
@@ -122556,12 +122923,13 @@ function EnabledEmailSettingsPage({ path, platformName, username }) {
122556
122923
  ] }),
122557
122924
  /* @__PURE__ */ jsx4("h2", { children: "Domain management" }),
122558
122925
  /* @__PURE__ */ jsx4("p", { children: "Who registers custom email domains for projects." }),
122559
- /* @__PURE__ */ jsxs("form", { method: "post", action: "/admin/email/settings", children: [
122926
+ /* @__PURE__ */ jsxs("form", { method: "post", action: "/admin/email/settings", className: "void-admin-email-choice-form", children: [
122560
122927
  /* @__PURE__ */ jsxs(
122561
122928
  RadioList,
122562
122929
  {
122563
122930
  label: "Domain management",
122564
122931
  isLabelHidden: true,
122932
+ size: "sm",
122565
122933
  htmlName: "domains",
122566
122934
  value: domainManagement,
122567
122935
  onChange: (value) => setDomainManagement(value),
@@ -122589,14 +122957,14 @@ function EnabledEmailSettingsPage({ path, platformName, username }) {
122589
122957
  ] })
122590
122958
  ] });
122591
122959
  }
122592
- var import_react153, META2, EMAIL_SETUP_GUIDE;
122960
+ var import_react155, META2, EMAIL_SETUP_GUIDE;
122593
122961
  var init_EmailSettingsPage = __esm({
122594
122962
  "../../home/runner/work/void/void/platform/packages/api/src/admin/react/views/EmailSettingsPage.tsx"() {
122595
122963
  "use strict";
122596
122964
  init_performance2();
122597
122965
  init_Button2();
122598
122966
  init_RadioList2();
122599
- import_react153 = __toESM(require_react(), 1);
122967
+ import_react155 = __toESM(require_react(), 1);
122600
122968
  init_lib2();
122601
122969
  init_views();
122602
122970
  init_FormControls();
@@ -122819,7 +123187,7 @@ function AdminUserTypeahead({
122819
123187
  excludedUserIds = noExcludedUsers
122820
123188
  }) {
122821
123189
  const fate = useFateClient();
122822
- const source = (0, import_react154.useMemo)(
123190
+ const source = (0, import_react156.useMemo)(
122823
123191
  () => ({
122824
123192
  bootstrap: /* @__PURE__ */ __name(() => [], "bootstrap"),
122825
123193
  search: /* @__PURE__ */ __name(async (query) => {
@@ -122862,13 +123230,13 @@ function AdminUserTypeahead({
122862
123230
  }
122863
123231
  );
122864
123232
  }
122865
- var import_react154, import_jsx_runtime117, noExcludedUsers;
123233
+ var import_react156, import_jsx_runtime117, noExcludedUsers;
122866
123234
  var init_AdminUserTypeahead = __esm({
122867
123235
  "../../home/runner/work/void/void/platform/packages/api/src/admin/react/views/AdminUserTypeahead.tsx"() {
122868
123236
  "use strict";
122869
123237
  init_performance2();
122870
123238
  init_Typeahead2();
122871
- import_react154 = __toESM(require_react(), 1);
123239
+ import_react156 = __toESM(require_react(), 1);
122872
123240
  init_lib2();
122873
123241
  init_views();
122874
123242
  import_jsx_runtime117 = __toESM(require_jsx_runtime(), 1);
@@ -122879,7 +123247,7 @@ var init_AdminUserTypeahead = __esm({
122879
123247
 
122880
123248
  // ../../home/runner/work/void/void/platform/packages/api/src/admin/react/views/ProjectOwnerTransferSection.tsx
122881
123249
  function ProjectOwnerTransferSection({ projectId }) {
122882
- const [selected, setSelected] = (0, import_react155.useState)(null);
123250
+ const [selected, setSelected] = (0, import_react157.useState)(null);
122883
123251
  return /* @__PURE__ */ (0, import_jsx_runtime118.jsxs)("section", { id: "owner-transfer", className: "void-admin-owner-transfer", children: [
122884
123252
  /* @__PURE__ */ (0, import_jsx_runtime118.jsx)("h2", { children: "Transfer Ownership" }),
122885
123253
  /* @__PURE__ */ (0, import_jsx_runtime118.jsx)("p", { children: "Transfer this project to an existing Void Platform user. You will review the ownership, plan, suspension, active-work, routing, and usage-counter changes before applying them." }),
@@ -122898,13 +123266,13 @@ function ProjectOwnerTransferSection({ projectId }) {
122898
123266
  )
122899
123267
  ] });
122900
123268
  }
122901
- var import_react155, import_jsx_runtime118;
123269
+ var import_react157, import_jsx_runtime118;
122902
123270
  var init_ProjectOwnerTransferSection = __esm({
122903
123271
  "../../home/runner/work/void/void/platform/packages/api/src/admin/react/views/ProjectOwnerTransferSection.tsx"() {
122904
123272
  "use strict";
122905
123273
  init_performance2();
122906
123274
  init_Button2();
122907
- import_react155 = __toESM(require_react(), 1);
123275
+ import_react157 = __toESM(require_react(), 1);
122908
123276
  init_AdminUserTypeahead();
122909
123277
  import_jsx_runtime118 = __toESM(require_jsx_runtime(), 1);
122910
123278
  __name(ProjectOwnerTransferSection, "ProjectOwnerTransferSection");
@@ -122919,8 +123287,8 @@ function ProjectTeamSection({
122919
123287
  invitations: invitations2,
122920
123288
  tz
122921
123289
  }) {
122922
- const [selected, setSelected] = (0, import_react156.useState)(null);
122923
- const excluded = (0, import_react156.useMemo)(
123290
+ const [selected, setSelected] = (0, import_react158.useState)(null);
123291
+ const excluded = (0, import_react158.useMemo)(
122924
123292
  () => /* @__PURE__ */ new Set([...owner ? [owner.id] : [], ...members2.map(({ user }) => user.id)]),
122925
123293
  [owner, members2]
122926
123294
  );
@@ -123057,14 +123425,14 @@ function ProjectTeamSection({
123057
123425
  ] })
123058
123426
  ] });
123059
123427
  }
123060
- var import_react156, import_jsx_runtime119, roleOptions;
123428
+ var import_react158, import_jsx_runtime119, roleOptions;
123061
123429
  var init_ProjectTeamSection = __esm({
123062
123430
  "../../home/runner/work/void/void/platform/packages/api/src/admin/react/views/ProjectTeamSection.tsx"() {
123063
123431
  "use strict";
123064
123432
  init_performance2();
123065
123433
  init_Button2();
123066
123434
  init_Table2();
123067
- import_react156 = __toESM(require_react(), 1);
123435
+ import_react158 = __toESM(require_react(), 1);
123068
123436
  init_AdminUserTypeahead();
123069
123437
  init_format();
123070
123438
  init_FormControls();
@@ -123387,46 +123755,37 @@ function ProjectsListPage({ path, platformName, username }) {
123387
123755
  throw new Error("Projects page data is unavailable.");
123388
123756
  }
123389
123757
  const { projects: projects2, search, page, totalPages, siteDomain, tz } = pageData;
123390
- const [filter, setFilter] = (0, import_react157.useState)(search ?? "");
123391
- (0, import_react157.useEffect)(() => setFilter(search ?? ""), [search]);
123392
- const visibleProjects = projects2.filter(
123393
- (project) => [project.slug, project.owner_login, project.type, project.last_deploy_status].some(
123394
- (value) => value?.toLowerCase().includes(filter.toLowerCase())
123395
- )
123396
- );
123397
- const suggestions = (0, import_react157.useMemo)(
123398
- () => projects2.map((project) => ({
123399
- id: project.id,
123400
- label: project.slug,
123401
- keywords: [project.owner_login, project.id, project.type, project.last_deploy_status],
123402
- description: project.owner_login ?? void 0
123403
- })),
123404
- [projects2]
123405
- );
123758
+ const [filter, setFilter] = (0, import_react159.useState)(search ?? "");
123759
+ (0, import_react159.useEffect)(() => setFilter(search ?? ""), [search]);
123760
+ const selectedProject = projects2.find((project) => project.id === search);
123406
123761
  return /* @__PURE__ */ jsxs(Layout2, { platformName, title: "Projects", username, children: [
123407
123762
  /* @__PURE__ */ jsx4("h1", { children: "Projects" }),
123408
123763
  /* @__PURE__ */ jsx4(
123409
123764
  SearchForm,
123410
123765
  {
123411
123766
  action: "/admin/projects",
123767
+ kind: "projects",
123412
123768
  label: "Search projects",
123413
123769
  value: filter,
123414
123770
  onChange: setFilter,
123415
- suggestions,
123416
123771
  initialValue: search,
123417
- showClear: Boolean(search)
123772
+ initialSelection: selectedProject ? {
123773
+ id: selectedProject.id,
123774
+ label: selectedProject.slug,
123775
+ description: selectedProject.owner_login ?? void 0
123776
+ } : void 0
123418
123777
  }
123419
123778
  ),
123420
- /* @__PURE__ */ jsx4(ProjectsTable, { projects: visibleProjects, siteDomain, tz }),
123779
+ /* @__PURE__ */ jsx4(ProjectsTable, { projects: projects2, siteDomain, tz }),
123421
123780
  page != null && totalPages != null && /* @__PURE__ */ jsx4(Pagination, { page, totalPages, baseUrl: "/admin/projects" })
123422
123781
  ] });
123423
123782
  }
123424
- var import_react157;
123783
+ var import_react159;
123425
123784
  var init_ProjectsListPage = __esm({
123426
123785
  "../../home/runner/work/void/void/platform/packages/api/src/admin/react/views/ProjectsListPage.tsx"() {
123427
123786
  "use strict";
123428
123787
  init_performance2();
123429
- import_react157 = __toESM(require_react(), 1);
123788
+ import_react159 = __toESM(require_react(), 1);
123430
123789
  init_lib2();
123431
123790
  init_views();
123432
123791
  init_Layout2();
@@ -123443,8 +123802,8 @@ function BooleanBadge({ value }) {
123443
123802
  return /* @__PURE__ */ jsx4(Badge, { label: value ? "Yes" : "No", variant: value ? "success" : "neutral" });
123444
123803
  }
123445
123804
  function CopyValue({ label, value }) {
123446
- const [copied, setCopied] = (0, import_react158.useState)(false);
123447
- const [failed, setFailed] = (0, import_react158.useState)(false);
123805
+ const [copied, setCopied] = (0, import_react160.useState)(false);
123806
+ const [failed, setFailed] = (0, import_react160.useState)(false);
123448
123807
  return /* @__PURE__ */ jsxs("div", { className: "void-admin-access-copy-value", children: [
123449
123808
  /* @__PURE__ */ jsxs("label", { children: [
123450
123809
  /* @__PURE__ */ jsx4("span", { children: label }),
@@ -123851,7 +124210,7 @@ function SignupControlPage({ path, platformName, username }) {
123851
124210
  ] }) })
123852
124211
  ] }) });
123853
124212
  }
123854
- var import_react158;
124213
+ var import_react160;
123855
124214
  var init_SignupControlPage = __esm({
123856
124215
  "../../home/runner/work/void/void/platform/packages/api/src/admin/react/views/SignupControlPage.tsx"() {
123857
124216
  "use strict";
@@ -123861,7 +124220,7 @@ var init_SignupControlPage = __esm({
123861
124220
  init_Card2();
123862
124221
  init_Table2();
123863
124222
  init_Text2();
123864
- import_react158 = __toESM(require_react(), 1);
124223
+ import_react160 = __toESM(require_react(), 1);
123865
124224
  init_lib2();
123866
124225
  init_views();
123867
124226
  init_format();
@@ -124082,34 +124441,25 @@ function UsersListPage({ path, platformName, username }) {
124082
124441
  throw new Error("Users page data is unavailable.");
124083
124442
  }
124084
124443
  const { users: users2, search, page, totalPages, tz } = pageData;
124085
- const [filter, setFilter] = (0, import_react159.useState)(search ?? "");
124086
- (0, import_react159.useEffect)(() => setFilter(search ?? ""), [search]);
124087
- const visibleUsers = users2.filter(
124088
- (user) => [user.login, user.email, user.id].some(
124089
- (value) => value?.toLowerCase().includes(filter.toLowerCase())
124090
- )
124091
- );
124092
- const suggestions = (0, import_react159.useMemo)(
124093
- () => users2.map((user) => ({
124094
- id: user.id,
124095
- label: user.login,
124096
- keywords: [user.email, user.id],
124097
- description: [user.email, user.id].filter(Boolean).join(" \xB7 ")
124098
- })),
124099
- [users2]
124100
- );
124444
+ const [filter, setFilter] = (0, import_react161.useState)(search ?? "");
124445
+ (0, import_react161.useEffect)(() => setFilter(search ?? ""), [search]);
124446
+ const selectedUser = users2.find((user) => user.id === search);
124101
124447
  return /* @__PURE__ */ jsxs(Layout2, { platformName, title: "Users", username, children: [
124102
124448
  /* @__PURE__ */ jsx4("h1", { children: "Users" }),
124103
124449
  /* @__PURE__ */ jsx4(
124104
124450
  SearchForm,
124105
124451
  {
124106
124452
  action: "/admin/users",
124453
+ kind: "users",
124107
124454
  label: "Search users",
124108
124455
  value: filter,
124109
124456
  onChange: setFilter,
124110
- suggestions,
124111
124457
  initialValue: search,
124112
- showClear: Boolean(search)
124458
+ initialSelection: selectedUser ? {
124459
+ id: selectedUser.id,
124460
+ label: selectedUser.login,
124461
+ description: [selectedUser.email, selectedUser.id].filter(Boolean).join(" \xB7 ")
124462
+ } : void 0
124113
124463
  }
124114
124464
  ),
124115
124465
  /* @__PURE__ */ jsxs(Table2, { hasHover: true, children: [
@@ -124123,7 +124473,7 @@ function UsersListPage({ path, platformName, username }) {
124123
124473
  /* @__PURE__ */ jsx4(TableHeaderCell, { children: "Created" }),
124124
124474
  /* @__PURE__ */ jsx4(TableHeaderCell, { children: "Actions" })
124125
124475
  ] }) }),
124126
- /* @__PURE__ */ jsx4(TableBody, { children: visibleUsers.map((user) => /* @__PURE__ */ jsxs(TableRow, { children: [
124476
+ /* @__PURE__ */ jsx4(TableBody, { children: users2.map((user) => /* @__PURE__ */ jsxs(TableRow, { children: [
124127
124477
  /* @__PURE__ */ jsx4(TableCell, { children: /* @__PURE__ */ jsx4("a", { href: `/admin/users/${user.id}`, children: user.login }) }),
124128
124478
  /* @__PURE__ */ jsx4(TableCell, { children: user.provider }),
124129
124479
  /* @__PURE__ */ jsx4(TableCell, { children: user.email ?? "-" }),
@@ -124143,18 +124493,18 @@ function UsersListPage({ path, platformName, username }) {
124143
124493
  ) })
124144
124494
  ] }, user.id)) })
124145
124495
  ] }),
124146
- visibleUsers.length === 0 && /* @__PURE__ */ jsx4("p", { children: /* @__PURE__ */ jsx4("em", { children: "No users found." }) }),
124496
+ users2.length === 0 && /* @__PURE__ */ jsx4("p", { children: /* @__PURE__ */ jsx4("em", { children: "No users found." }) }),
124147
124497
  page != null && totalPages != null && /* @__PURE__ */ jsx4(Pagination, { page, totalPages, baseUrl: "/admin/users" })
124148
124498
  ] });
124149
124499
  }
124150
- var import_react159;
124500
+ var import_react161;
124151
124501
  var init_UsersListPage = __esm({
124152
124502
  "../../home/runner/work/void/void/platform/packages/api/src/admin/react/views/UsersListPage.tsx"() {
124153
124503
  "use strict";
124154
124504
  init_performance2();
124155
124505
  init_Button2();
124156
124506
  init_Table2();
124157
- import_react159 = __toESM(require_react(), 1);
124507
+ import_react161 = __toESM(require_react(), 1);
124158
124508
  init_lib2();
124159
124509
  init_views();
124160
124510
  init_format();
@@ -144178,7 +144528,7 @@ var import_headless = __toESM(require_xterm_headless(), 1);
144178
144528
  // ../../home/runner/work/void/void/packages/void/package.json
144179
144529
  var package_default = {
144180
144530
  name: "void",
144181
- version: "0.21.1",
144531
+ version: "0.21.3",
144182
144532
  repository: {
144183
144533
  type: "git",
144184
144534
  url: "git+https://github.com/voidzero-dev/void.git",
@@ -145762,7 +146112,9 @@ async function formatLogTailMarkdown(lines, totalCaptured) {
145762
146112
  if (groups.slice(0, selectedStart).some((group) => group.requiresTerminal)) {
145763
146113
  return null;
145764
146114
  }
145765
- const visibleLines = await renderTerminalGroups(selectedNewestFirst.reverse());
146115
+ const visibleLines = (await renderTerminalGroups(selectedNewestFirst.reverse())).map(
146116
+ (line2) => line2.replace(/^ {257,}/, " ".repeat(32))
146117
+ );
145766
146118
  const keptNewestFirst = [];
145767
146119
  let joinedBytes = 0;
145768
146120
  for (let lineIndex = visibleLines.length - 1; lineIndex >= 0; lineIndex--) {
@@ -147383,9 +147735,15 @@ var BuildScheduler = class extends DurableObject2 {
147383
147735
  }
147384
147736
  return;
147385
147737
  }
147738
+ const usersById = new Map(
147739
+ (await getUsersByIds(db, [...new Set(queued.map((build) => build.user_id))])).map((user) => [
147740
+ user.id,
147741
+ user
147742
+ ])
147743
+ );
147386
147744
  const saturatedNamespaces = /* @__PURE__ */ new Set();
147387
147745
  for (const build of queued) {
147388
- const user = await getUser(db, build.user_id);
147746
+ const user = usersById.get(build.user_id);
147389
147747
  if (!user) {
147390
147748
  await updateBuildStatus(db, build.id, "failed", {
147391
147749
  error: "owning user not found",
@@ -148155,6 +148513,8 @@ init_schema2();
148155
148513
  init_suspension();
148156
148514
  var DEPLOY_TOKEN_ALLOWLIST = [
148157
148515
  { method: "GET", segments: ["projects"] },
148516
+ { method: "GET", segments: ["projects", "resolve"] },
148517
+ { method: "GET", segments: ["projects", null] },
148158
148518
  { method: "GET", segments: ["projects", null, "resources"] },
148159
148519
  { method: "POST", segments: ["projects", null, "database"] },
148160
148520
  { method: "POST", segments: ["projects", null, "prerender-token"] },
@@ -149424,24 +149784,7 @@ init_dist2();
149424
149784
 
149425
149785
  // ../../home/runner/work/void/void/platform/packages/api/src/asset-inventory.ts
149426
149786
  init_performance2();
149427
-
149428
- // ../../home/runner/work/void/void/platform/packages/api/src/concurrency.ts
149429
- init_performance2();
149430
- var R2_CONCURRENCY = 6;
149431
- async function uploadPool(items, concurrency, fn) {
149432
- let nextIndex = 0;
149433
- async function worker() {
149434
- while (nextIndex < items.length) {
149435
- const item = items[nextIndex++];
149436
- await fn(item);
149437
- }
149438
- }
149439
- __name(worker, "worker");
149440
- await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
149441
- }
149442
- __name(uploadPool, "uploadPool");
149443
-
149444
- // ../../home/runner/work/void/void/platform/packages/api/src/asset-inventory.ts
149787
+ init_concurrency();
149445
149788
  async function findR2Assets(r2, projectId, requiredHashes) {
149446
149789
  const found = /* @__PURE__ */ new Map();
149447
149790
  if (requiredHashes.size === 0) {
@@ -149474,6 +149817,38 @@ async function findR2Assets(r2, projectId, requiredHashes) {
149474
149817
  return found;
149475
149818
  }
149476
149819
  __name(findR2Assets, "findR2Assets");
149820
+ function persistedR2AssetSizes(raw2) {
149821
+ const sizes = /* @__PURE__ */ new Map();
149822
+ const conflicting = /* @__PURE__ */ new Set();
149823
+ if (!raw2) {
149824
+ return sizes;
149825
+ }
149826
+ let manifest;
149827
+ try {
149828
+ manifest = JSON.parse(raw2);
149829
+ } catch {
149830
+ return sizes;
149831
+ }
149832
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
149833
+ return sizes;
149834
+ }
149835
+ for (const value of Object.values(manifest)) {
149836
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
149837
+ continue;
149838
+ }
149839
+ const entry = value;
149840
+ if (entry.store === "r2" && typeof entry.hash === "string" && /^[0-9a-f]{32}$/.test(entry.hash) && typeof entry.size === "number" && Number.isSafeInteger(entry.size) && entry.size >= 0) {
149841
+ if (sizes.has(entry.hash) && sizes.get(entry.hash) !== entry.size) {
149842
+ conflicting.add(entry.hash);
149843
+ sizes.delete(entry.hash);
149844
+ } else if (!conflicting.has(entry.hash)) {
149845
+ sizes.set(entry.hash, entry.size);
149846
+ }
149847
+ }
149848
+ }
149849
+ return sizes;
149850
+ }
149851
+ __name(persistedR2AssetSizes, "persistedR2AssetSizes");
149477
149852
  async function findMissingR2Assets(r2, projectId, manifest) {
149478
149853
  const entries2 = Object.entries(manifest);
149479
149854
  const found = await findR2Assets(r2, projectId, new Set(entries2.map(([, entry]) => entry.hash)));
@@ -149486,6 +149861,7 @@ init_assets();
149486
149861
  init_hmac();
149487
149862
  init_capabilities();
149488
149863
  init_cf_api();
149864
+ init_concurrency();
149489
149865
  init_cron_scheduler_client();
149490
149866
  init_db2();
149491
149867
  init_email();
@@ -149867,6 +150243,9 @@ async function ensureResource(db, accountId, apiToken, projectId, type, bindingN
149867
150243
  const resourceName = `${projectId}-${bindingName}`.toLowerCase().replaceAll("_", "-");
149868
150244
  const found = existing.find((r) => r.type === type && r.binding_name === bindingName);
149869
150245
  if (found) {
150246
+ if (type === "d1") {
150247
+ await enableD1ReadReplication(accountId, apiToken, found.cf_id);
150248
+ }
149870
150249
  if (type === "queue" && !UUID_RE.test(found.cf_id)) {
149871
150250
  const queueInfo = await getQueueByName(accountId, apiToken, resourceName);
149872
150251
  await db.update(resources).set({ cf_id: queueInfo.queue_id }).where(eq(resources.id, found.id));
@@ -149882,6 +150261,9 @@ async function ensureResource(db, accountId, apiToken, projectId, type, bindingN
149882
150261
  } catch (err) {
149883
150262
  if (isAlreadyExistsError3(err)) {
149884
150263
  cfId = await lookupCfResource(type, accountId, apiToken, resourceName);
150264
+ if (type === "d1") {
150265
+ await enableD1ReadReplication(accountId, apiToken, cfId);
150266
+ }
149885
150267
  } else {
149886
150268
  throw err;
149887
150269
  }
@@ -152376,6 +152758,19 @@ async function handleManifestOnlyDeploy(context) {
152376
152758
  });
152377
152759
  }, "withProjectAssetGcFence");
152378
152760
  let remainingAssetGcHashBudget = ASSET_GC_MAX_HASHES_PER_DEPLOY;
152761
+ let orphanSweepAfterResponse = null;
152762
+ const finishOrphanSweep = /* @__PURE__ */ __name(async () => {
152763
+ const sweep = orphanSweepAfterResponse;
152764
+ if (!sweep) {
152765
+ return;
152766
+ }
152767
+ const work = Promise.resolve().then(sweep);
152768
+ try {
152769
+ context.executionCtx.waitUntil(work);
152770
+ } catch {
152771
+ await work;
152772
+ }
152773
+ }, "finishOrphanSweep");
152379
152774
  const deleteAssetHashesWithProjectFence = /* @__PURE__ */ __name(async (knownRetainedHashes, hashes) => {
152380
152775
  let deleted = 0;
152381
152776
  const budgetedHashes = hashes.slice(0, remainingAssetGcHashBudget);
@@ -152519,6 +152914,15 @@ async function handleManifestOnlyDeploy(context) {
152519
152914
  const declaredHashes = Array.from(
152520
152915
  new Set(Object.values(incomingAssetManifest).map((e) => e.hash))
152521
152916
  );
152917
+ const activeR2Sizes = currentLiveDeployment?.status === "live" && currentLiveDeployment.project_id === projectId ? persistedR2AssetSizes(currentLiveDeployment.asset_hashes) : /* @__PURE__ */ new Map();
152918
+ const declaredSizeByHash = /* @__PURE__ */ new Map();
152919
+ for (const entry of Object.values(incomingAssetManifest)) {
152920
+ if (!declaredSizeByHash.has(entry.hash)) {
152921
+ declaredSizeByHash.set(entry.hash, entry.size);
152922
+ } else if (declaredSizeByHash.get(entry.hash) !== entry.size) {
152923
+ declaredSizeByHash.set(entry.hash, null);
152924
+ }
152925
+ }
152522
152926
  let previousBackendIsStatic = false;
152523
152927
  if (currentLiveDeployment) {
152524
152928
  const routingRaw = await context.env.ROUTING.get(project.slug);
@@ -152530,7 +152934,7 @@ async function handleManifestOnlyDeploy(context) {
152530
152934
  }
152531
152935
  }
152532
152936
  }
152533
- const newHashes = hasWorkerFiles || !previousBackendIsStatic ? declaredHashes : declaredHashes.filter((hash4) => !carryForwardHashes.has(hash4));
152937
+ const newHashes = hasWorkerFiles ? declaredHashes.filter((hash4) => activeR2Sizes.get(hash4) !== declaredSizeByHash.get(hash4)) : !previousBackendIsStatic ? declaredHashes : declaredHashes.filter((hash4) => !carryForwardHashes.has(hash4));
152534
152938
  const missingHashes = [];
152535
152939
  const r2HeadCache = /* @__PURE__ */ new Map();
152536
152940
  const foundNewAssets = await findR2Assets(
@@ -152995,16 +153399,18 @@ ${Date.now()}
152995
153399
  await context.env.ROUTING.delete(`manifest:${old.id}`);
152996
153400
  }
152997
153401
  if (assetCleanupSafe) {
152998
- try {
152999
- const orphans = await sweepAssetOrphansWithProjectFence(retainedHashes);
153000
- if (orphans > 0) {
153001
- logger.info("retention_cleanup", "r2_orphans_swept", { count: orphans });
153402
+ orphanSweepAfterResponse = /* @__PURE__ */ __name(async () => {
153403
+ try {
153404
+ const orphans = await sweepAssetOrphansWithProjectFence(retainedHashes);
153405
+ if (orphans > 0) {
153406
+ logger.info("retention_cleanup", "r2_orphans_swept", { count: orphans });
153407
+ }
153408
+ } catch (e) {
153409
+ logger.warn("retention_cleanup", "r2_orphan_sweep_failed", {
153410
+ err: e instanceof Error ? e.message : String(e)
153411
+ });
153002
153412
  }
153003
- } catch (e) {
153004
- logger.warn("retention_cleanup", "r2_orphan_sweep_failed", {
153005
- err: e instanceof Error ? e.message : String(e)
153006
- });
153007
- }
153413
+ }, "orphanSweepAfterResponse");
153008
153414
  }
153009
153415
  }
153010
153416
  const application2 = readPlatformApplication(context.env);
@@ -153021,6 +153427,7 @@ ${Date.now()}
153021
153427
  assets: assetCount,
153022
153428
  workers: 0
153023
153429
  });
153430
+ await finishOrphanSweep();
153024
153431
  return;
153025
153432
  }
153026
153433
  currentStep = "provisioning";
@@ -154065,16 +154472,18 @@ ${Date.now()}
154065
154472
  }
154066
154473
  }
154067
154474
  if (assetCleanupSafe) {
154068
- try {
154069
- const orphans = await sweepAssetOrphansWithProjectFence(retainedHashes);
154070
- if (orphans > 0) {
154071
- logger.info("retention_cleanup", "r2_orphans_swept", { count: orphans });
154475
+ orphanSweepAfterResponse = /* @__PURE__ */ __name(async () => {
154476
+ try {
154477
+ const orphans = await sweepAssetOrphansWithProjectFence(retainedHashes);
154478
+ if (orphans > 0) {
154479
+ logger.info("retention_cleanup", "r2_orphans_swept", { count: orphans });
154480
+ }
154481
+ } catch (e) {
154482
+ logger.warn("retention_cleanup", "r2_orphan_sweep_failed", {
154483
+ err: e instanceof Error ? e.message : String(e)
154484
+ });
154072
154485
  }
154073
- } catch (e) {
154074
- logger.warn("retention_cleanup", "r2_orphan_sweep_failed", {
154075
- err: e instanceof Error ? e.message : String(e)
154076
- });
154077
- }
154486
+ }, "orphanSweepAfterResponse");
154078
154487
  }
154079
154488
  }
154080
154489
  const application = readPlatformApplication(context.env);
@@ -154092,6 +154501,7 @@ ${Date.now()}
154092
154501
  workers: workerFiles.length,
154093
154502
  ...migrationsApplied > 0 && { migrations: migrationsApplied }
154094
154503
  });
154504
+ await finishOrphanSweep();
154095
154505
  } catch (err) {
154096
154506
  const raw2 = err instanceof ProjectAccessDeniedError ? err.userMessage : err instanceof Error ? err.message : String(err);
154097
154507
  const msg = sanitizeDeployErrorMessage(raw2);
@@ -154559,11 +154969,14 @@ app11.post("/:id/deploy/preflight", async (context) => {
154559
154969
  if (!valid) {
154560
154970
  return context.json({ needed: Object.keys(assetManifest), total, skipped: 0 });
154561
154971
  }
154562
- const needed2 = await findMissingR2Assets(
154563
- context.env.STATIC_ASSETS_R2,
154564
- projectId,
154565
- assetManifest
154972
+ const active = project.active_deployment_id ? await getDeployment(context.get("db"), project.active_deployment_id) : null;
154973
+ const activeR2Sizes = active?.status === "live" && active.project_id === projectId ? persistedR2AssetSizes(active.asset_hashes) : /* @__PURE__ */ new Map();
154974
+ const unverified = Object.fromEntries(
154975
+ Object.entries(assetManifest).filter(
154976
+ ([, entry]) => activeR2Sizes.get(entry.hash) !== entry.size
154977
+ )
154566
154978
  );
154979
+ const needed2 = await findMissingR2Assets(context.env.STATIC_ASSETS_R2, projectId, unverified);
154567
154980
  return context.json({ needed: needed2, total, skipped: total - needed2.length });
154568
154981
  }
154569
154982
  let activeDeploymentId = project.active_deployment_id;
@@ -156323,6 +156736,7 @@ async function tryActivateDomain(env2, db, domain2, project, options) {
156323
156736
  __name(tryActivateDomain, "tryActivateDomain");
156324
156737
 
156325
156738
  // ../../home/runner/work/void/void/platform/packages/api/src/index.ts
156739
+ init_cleanup();
156326
156740
  init_cloudflare_api();
156327
156741
  init_email_destination_cleanup();
156328
156742
  init_sandbox_controller();
@@ -160739,6 +161153,7 @@ init_db2();
160739
161153
  // ../../home/runner/work/void/void/platform/packages/api/src/revalidate-core.ts
160740
161154
  init_performance2();
160741
161155
  init_dist();
161156
+ init_concurrency();
160742
161157
  init_db2();
160743
161158
 
160744
161159
  // ../../home/runner/work/void/void/platform/packages/api/src/isr-cache.ts
@@ -162403,6 +162818,35 @@ var app30 = new Hono2().get("/", async (context) => {
162403
162818
  };
162404
162819
  });
162405
162820
  return context.json(projectsWithSummaries.sort(sortProjectList));
162821
+ }).get("/choices", async (context) => {
162822
+ const projects2 = await getAccessibleProjectsByUserId(context.get("db"), context.get("user").id);
162823
+ return context.json(
162824
+ projects2.map(({ project, role, owner }) => ({
162825
+ ...projectWithUrl(project, context.env),
162826
+ role,
162827
+ owner: { id: owner.id, login: owner.login }
162828
+ }))
162829
+ );
162830
+ }).get("/resolve", async (context) => {
162831
+ const slug = context.req.query("slug");
162832
+ if (!slug || slug.length > MAX_SLUG_LENGTH || !/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(slug)) {
162833
+ return context.json({ error: "valid slug is required" }, 400);
162834
+ }
162835
+ const access = await getAccessibleProjectBySlug(
162836
+ context.get("db"),
162837
+ context.get("user").id,
162838
+ slug
162839
+ );
162840
+ if (!access || context.get("deployScope") && context.get("deployScope") !== access.project.id) {
162841
+ return context.json({ project: null });
162842
+ }
162843
+ return context.json({
162844
+ project: {
162845
+ ...projectWithUrl(access.project, context.env),
162846
+ role: access.role,
162847
+ owner: { id: access.owner.id, login: access.owner.login }
162848
+ }
162849
+ });
162406
162850
  }).post("/", async (context) => {
162407
162851
  const body = await context.req.json();
162408
162852
  if (!body.slug) {
@@ -166147,6 +166591,9 @@ async function scheduled(controller, env2, _ctx) {
166147
166591
  }
166148
166592
  const db = createDb(env2.HYPERDRIVE.connectionString);
166149
166593
  const capabilities = getPlatformCapabilities(env2);
166594
+ await reconcileProjectCleanupJobs(db, env2).catch(
166595
+ (error63) => console.error("[scheduled] project cleanup retry failed:", error63)
166596
+ );
166150
166597
  if (capabilities.email) {
166151
166598
  await reconcileEmailDestinationCleanups(db, {
166152
166599
  accountId: env2.CF_ACCOUNT_ID,