Webhooks for the AT Protocol airglow.run
atproto atprotocol automation webhook
12
fork

Configure Feed

Select the types of activity you want to include in your feed.

perf: better automation search

Hugo 09698899 61a3a37f

+592 -10
+6 -1
app/islands/AutomationFilters.tsx
··· 16 16 17 17 const DEBOUNCE_MS = 300; 18 18 const BUSY_DELAY_MS = 120; 19 + const MIN_Q_LENGTH = 2; 19 20 const RESULTS_ID = "automation-results"; 20 21 21 22 const buildDisplayUrl = (params: URLSearchParams) => { ··· 134 135 setQValue(value); 135 136 if (timerRef.current) clearTimeout(timerRef.current); 136 137 timerRef.current = setTimeout(() => { 137 - if (value.trim() !== lastSubmittedQRef.current) { 138 + const trimmed = value.trim(); 139 + // Single-char queries match nearly everything (four leading-wildcard LIKEs). 140 + // Treat them as empty — clearing the box still fires. 141 + if (trimmed.length > 0 && trimmed.length < MIN_Q_LENGTH) return; 142 + if (trimmed !== lastSubmittedQRef.current) { 138 143 void run(paramsFromFilters(value, lexiconValue)); 139 144 } 140 145 }, DEBOUNCE_MS);
+13 -8
app/routes/automations.tsx
··· 23 23 const q = (c.req.query("q") ?? "").trim().slice(0, 128); 24 24 const lexicon = (c.req.query("lexicon") ?? "").trim().slice(0, 256); 25 25 const limit = Math.min(Math.max(Number(c.req.query("limit")) || PAGE_SIZE, PAGE_SIZE), 200); 26 + const isFragment = c.req.header("X-Fragment") === "1"; 26 27 27 28 const featuredUris = getFeaturedUris(); 28 29 const showFeatured = !q && !lexicon; ··· 38 39 const hasMore = rows.length > limit; 39 40 const visible = hasMore ? rows.slice(0, limit) : rows; 40 41 41 - const lexiconRows = await db 42 - .select({ lexicon: automations.lexicon, count: count() }) 43 - .from(automations) 44 - .where(eq(automations.active, true)) 45 - .groupBy(automations.lexicon) 46 - .orderBy(desc(sql`count(*)`)) 47 - .limit(50); 42 + // Fragment responses don't render the lexicon dropdown — the island keeps 43 + // its existing options — so skip the GROUP BY on every debounced keystroke. 44 + const lexiconRows = isFragment 45 + ? [] 46 + : await db 47 + .select({ lexicon: automations.lexicon, count: count() }) 48 + .from(automations) 49 + .where(eq(automations.active, true)) 50 + .groupBy(automations.lexicon) 51 + .orderBy(desc(sql`count(*)`)) 52 + .limit(50); 48 53 49 54 const buildQuery = (overrides: { limit?: number }) => { 50 55 const p = new URLSearchParams(); ··· 118 123 </div> 119 124 ); 120 125 121 - if (c.req.header("X-Fragment") === "1") { 126 + if (isFragment) { 122 127 return c.html(results); 123 128 } 124 129
+1
lib/db/migrations/0006_next_wendell_rand.sql
··· 1 + CREATE INDEX `automations_active_indexed_at_idx` ON `automations` (`active`,`indexed_at`);
+558
lib/db/migrations/meta/0006_snapshot.json
··· 1 + { 2 + "version": "6", 3 + "dialect": "sqlite", 4 + "id": "b0b35a17-c3c4-48be-8978-59eea412ec36", 5 + "prevId": "19fc8f74-84f5-4519-9612-b206e36e8133", 6 + "tables": { 7 + "automations": { 8 + "name": "automations", 9 + "columns": { 10 + "uri": { 11 + "name": "uri", 12 + "type": "text", 13 + "primaryKey": true, 14 + "notNull": true, 15 + "autoincrement": false 16 + }, 17 + "did": { 18 + "name": "did", 19 + "type": "text", 20 + "primaryKey": false, 21 + "notNull": true, 22 + "autoincrement": false 23 + }, 24 + "rkey": { 25 + "name": "rkey", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": true, 29 + "autoincrement": false 30 + }, 31 + "name": { 32 + "name": "name", 33 + "type": "text", 34 + "primaryKey": false, 35 + "notNull": true, 36 + "autoincrement": false 37 + }, 38 + "description": { 39 + "name": "description", 40 + "type": "text", 41 + "primaryKey": false, 42 + "notNull": false, 43 + "autoincrement": false 44 + }, 45 + "lexicon": { 46 + "name": "lexicon", 47 + "type": "text", 48 + "primaryKey": false, 49 + "notNull": true, 50 + "autoincrement": false 51 + }, 52 + "operation": { 53 + "name": "operation", 54 + "type": "text", 55 + "primaryKey": false, 56 + "notNull": true, 57 + "autoincrement": false, 58 + "default": "'[\"create\"]'" 59 + }, 60 + "actions": { 61 + "name": "actions", 62 + "type": "text", 63 + "primaryKey": false, 64 + "notNull": true, 65 + "autoincrement": false, 66 + "default": "'[]'" 67 + }, 68 + "fetches": { 69 + "name": "fetches", 70 + "type": "text", 71 + "primaryKey": false, 72 + "notNull": true, 73 + "autoincrement": false, 74 + "default": "'[]'" 75 + }, 76 + "conditions": { 77 + "name": "conditions", 78 + "type": "text", 79 + "primaryKey": false, 80 + "notNull": true, 81 + "autoincrement": false, 82 + "default": "'[]'" 83 + }, 84 + "active": { 85 + "name": "active", 86 + "type": "integer", 87 + "primaryKey": false, 88 + "notNull": true, 89 + "autoincrement": false, 90 + "default": false 91 + }, 92 + "dry_run": { 93 + "name": "dry_run", 94 + "type": "integer", 95 + "primaryKey": false, 96 + "notNull": true, 97 + "autoincrement": false, 98 + "default": false 99 + }, 100 + "indexed_at": { 101 + "name": "indexed_at", 102 + "type": "integer", 103 + "primaryKey": false, 104 + "notNull": true, 105 + "autoincrement": false 106 + } 107 + }, 108 + "indexes": { 109 + "automations_did_idx": { 110 + "name": "automations_did_idx", 111 + "columns": [ 112 + "did" 113 + ], 114 + "isUnique": false 115 + }, 116 + "automations_active_indexed_at_idx": { 117 + "name": "automations_active_indexed_at_idx", 118 + "columns": [ 119 + "active", 120 + "indexed_at" 121 + ], 122 + "isUnique": false 123 + } 124 + }, 125 + "foreignKeys": {}, 126 + "compositePrimaryKeys": {}, 127 + "uniqueConstraints": {}, 128 + "checkConstraints": {} 129 + }, 130 + "delivery_logs": { 131 + "name": "delivery_logs", 132 + "columns": { 133 + "id": { 134 + "name": "id", 135 + "type": "integer", 136 + "primaryKey": true, 137 + "notNull": true, 138 + "autoincrement": true 139 + }, 140 + "automation_uri": { 141 + "name": "automation_uri", 142 + "type": "text", 143 + "primaryKey": false, 144 + "notNull": true, 145 + "autoincrement": false 146 + }, 147 + "action_index": { 148 + "name": "action_index", 149 + "type": "integer", 150 + "primaryKey": false, 151 + "notNull": true, 152 + "autoincrement": false, 153 + "default": 0 154 + }, 155 + "event_time_us": { 156 + "name": "event_time_us", 157 + "type": "integer", 158 + "primaryKey": false, 159 + "notNull": true, 160 + "autoincrement": false 161 + }, 162 + "payload": { 163 + "name": "payload", 164 + "type": "text", 165 + "primaryKey": false, 166 + "notNull": false, 167 + "autoincrement": false 168 + }, 169 + "status_code": { 170 + "name": "status_code", 171 + "type": "integer", 172 + "primaryKey": false, 173 + "notNull": false, 174 + "autoincrement": false 175 + }, 176 + "message": { 177 + "name": "message", 178 + "type": "text", 179 + "primaryKey": false, 180 + "notNull": false, 181 + "autoincrement": false 182 + }, 183 + "error": { 184 + "name": "error", 185 + "type": "text", 186 + "primaryKey": false, 187 + "notNull": false, 188 + "autoincrement": false 189 + }, 190 + "dry_run": { 191 + "name": "dry_run", 192 + "type": "integer", 193 + "primaryKey": false, 194 + "notNull": true, 195 + "autoincrement": false, 196 + "default": false 197 + }, 198 + "attempt": { 199 + "name": "attempt", 200 + "type": "integer", 201 + "primaryKey": false, 202 + "notNull": true, 203 + "autoincrement": false, 204 + "default": 1 205 + }, 206 + "created_at": { 207 + "name": "created_at", 208 + "type": "integer", 209 + "primaryKey": false, 210 + "notNull": true, 211 + "autoincrement": false 212 + } 213 + }, 214 + "indexes": { 215 + "delivery_logs_automation_uri_id_idx": { 216 + "name": "delivery_logs_automation_uri_id_idx", 217 + "columns": [ 218 + "automation_uri", 219 + "id" 220 + ], 221 + "isUnique": false 222 + } 223 + }, 224 + "foreignKeys": { 225 + "delivery_logs_automation_uri_automations_uri_fk": { 226 + "name": "delivery_logs_automation_uri_automations_uri_fk", 227 + "tableFrom": "delivery_logs", 228 + "tableTo": "automations", 229 + "columnsFrom": [ 230 + "automation_uri" 231 + ], 232 + "columnsTo": [ 233 + "uri" 234 + ], 235 + "onDelete": "cascade", 236 + "onUpdate": "no action" 237 + } 238 + }, 239 + "compositePrimaryKeys": {}, 240 + "uniqueConstraints": {}, 241 + "checkConstraints": {} 242 + }, 243 + "favicon_cache": { 244 + "name": "favicon_cache", 245 + "columns": { 246 + "domain": { 247 + "name": "domain", 248 + "type": "text", 249 + "primaryKey": true, 250 + "notNull": true, 251 + "autoincrement": false 252 + }, 253 + "data": { 254 + "name": "data", 255 + "type": "text", 256 + "primaryKey": false, 257 + "notNull": true, 258 + "autoincrement": false 259 + }, 260 + "content_type": { 261 + "name": "content_type", 262 + "type": "text", 263 + "primaryKey": false, 264 + "notNull": true, 265 + "autoincrement": false 266 + }, 267 + "fetched_at": { 268 + "name": "fetched_at", 269 + "type": "integer", 270 + "primaryKey": false, 271 + "notNull": true, 272 + "autoincrement": false 273 + } 274 + }, 275 + "indexes": {}, 276 + "foreignKeys": {}, 277 + "compositePrimaryKeys": {}, 278 + "uniqueConstraints": {}, 279 + "checkConstraints": {} 280 + }, 281 + "lexicon_cache": { 282 + "name": "lexicon_cache", 283 + "columns": { 284 + "nsid": { 285 + "name": "nsid", 286 + "type": "text", 287 + "primaryKey": true, 288 + "notNull": true, 289 + "autoincrement": false 290 + }, 291 + "schema": { 292 + "name": "schema", 293 + "type": "text", 294 + "primaryKey": false, 295 + "notNull": true, 296 + "autoincrement": false 297 + }, 298 + "fetched_at": { 299 + "name": "fetched_at", 300 + "type": "integer", 301 + "primaryKey": false, 302 + "notNull": true, 303 + "autoincrement": false 304 + } 305 + }, 306 + "indexes": {}, 307 + "foreignKeys": {}, 308 + "compositePrimaryKeys": {}, 309 + "uniqueConstraints": {}, 310 + "checkConstraints": {} 311 + }, 312 + "oauth_sessions": { 313 + "name": "oauth_sessions", 314 + "columns": { 315 + "key": { 316 + "name": "key", 317 + "type": "text", 318 + "primaryKey": true, 319 + "notNull": true, 320 + "autoincrement": false 321 + }, 322 + "value": { 323 + "name": "value", 324 + "type": "text", 325 + "primaryKey": false, 326 + "notNull": true, 327 + "autoincrement": false 328 + }, 329 + "expires_at": { 330 + "name": "expires_at", 331 + "type": "integer", 332 + "primaryKey": false, 333 + "notNull": false, 334 + "autoincrement": false 335 + } 336 + }, 337 + "indexes": {}, 338 + "foreignKeys": {}, 339 + "compositePrimaryKeys": {}, 340 + "uniqueConstraints": {}, 341 + "checkConstraints": {} 342 + }, 343 + "oauth_states": { 344 + "name": "oauth_states", 345 + "columns": { 346 + "key": { 347 + "name": "key", 348 + "type": "text", 349 + "primaryKey": true, 350 + "notNull": true, 351 + "autoincrement": false 352 + }, 353 + "value": { 354 + "name": "value", 355 + "type": "text", 356 + "primaryKey": false, 357 + "notNull": true, 358 + "autoincrement": false 359 + }, 360 + "expires_at": { 361 + "name": "expires_at", 362 + "type": "integer", 363 + "primaryKey": false, 364 + "notNull": false, 365 + "autoincrement": false 366 + } 367 + }, 368 + "indexes": {}, 369 + "foreignKeys": {}, 370 + "compositePrimaryKeys": {}, 371 + "uniqueConstraints": {}, 372 + "checkConstraints": {} 373 + }, 374 + "secret_events": { 375 + "name": "secret_events", 376 + "columns": { 377 + "id": { 378 + "name": "id", 379 + "type": "integer", 380 + "primaryKey": true, 381 + "notNull": true, 382 + "autoincrement": true 383 + }, 384 + "did": { 385 + "name": "did", 386 + "type": "text", 387 + "primaryKey": false, 388 + "notNull": true, 389 + "autoincrement": false 390 + }, 391 + "name": { 392 + "name": "name", 393 + "type": "text", 394 + "primaryKey": false, 395 + "notNull": true, 396 + "autoincrement": false 397 + }, 398 + "action": { 399 + "name": "action", 400 + "type": "text", 401 + "primaryKey": false, 402 + "notNull": true, 403 + "autoincrement": false 404 + }, 405 + "created_at": { 406 + "name": "created_at", 407 + "type": "integer", 408 + "primaryKey": false, 409 + "notNull": true, 410 + "autoincrement": false 411 + } 412 + }, 413 + "indexes": {}, 414 + "foreignKeys": {}, 415 + "compositePrimaryKeys": {}, 416 + "uniqueConstraints": {}, 417 + "checkConstraints": {} 418 + }, 419 + "user_secrets": { 420 + "name": "user_secrets", 421 + "columns": { 422 + "id": { 423 + "name": "id", 424 + "type": "integer", 425 + "primaryKey": true, 426 + "notNull": true, 427 + "autoincrement": true 428 + }, 429 + "did": { 430 + "name": "did", 431 + "type": "text", 432 + "primaryKey": false, 433 + "notNull": true, 434 + "autoincrement": false 435 + }, 436 + "name": { 437 + "name": "name", 438 + "type": "text", 439 + "primaryKey": false, 440 + "notNull": true, 441 + "autoincrement": false 442 + }, 443 + "encrypted_value": { 444 + "name": "encrypted_value", 445 + "type": "blob", 446 + "primaryKey": false, 447 + "notNull": true, 448 + "autoincrement": false 449 + }, 450 + "created_at": { 451 + "name": "created_at", 452 + "type": "integer", 453 + "primaryKey": false, 454 + "notNull": true, 455 + "autoincrement": false 456 + }, 457 + "updated_at": { 458 + "name": "updated_at", 459 + "type": "integer", 460 + "primaryKey": false, 461 + "notNull": true, 462 + "autoincrement": false 463 + } 464 + }, 465 + "indexes": { 466 + "user_secrets_did_name_unique": { 467 + "name": "user_secrets_did_name_unique", 468 + "columns": [ 469 + "did", 470 + "name" 471 + ], 472 + "isUnique": true 473 + } 474 + }, 475 + "foreignKeys": { 476 + "user_secrets_did_users_did_fk": { 477 + "name": "user_secrets_did_users_did_fk", 478 + "tableFrom": "user_secrets", 479 + "tableTo": "users", 480 + "columnsFrom": [ 481 + "did" 482 + ], 483 + "columnsTo": [ 484 + "did" 485 + ], 486 + "onDelete": "cascade", 487 + "onUpdate": "no action" 488 + } 489 + }, 490 + "compositePrimaryKeys": {}, 491 + "uniqueConstraints": {}, 492 + "checkConstraints": {} 493 + }, 494 + "users": { 495 + "name": "users", 496 + "columns": { 497 + "id": { 498 + "name": "id", 499 + "type": "integer", 500 + "primaryKey": true, 501 + "notNull": true, 502 + "autoincrement": true 503 + }, 504 + "did": { 505 + "name": "did", 506 + "type": "text", 507 + "primaryKey": false, 508 + "notNull": true, 509 + "autoincrement": false 510 + }, 511 + "handle": { 512 + "name": "handle", 513 + "type": "text", 514 + "primaryKey": false, 515 + "notNull": true, 516 + "autoincrement": false 517 + }, 518 + "scope": { 519 + "name": "scope", 520 + "type": "text", 521 + "primaryKey": false, 522 + "notNull": false, 523 + "autoincrement": false 524 + }, 525 + "created_at": { 526 + "name": "created_at", 527 + "type": "integer", 528 + "primaryKey": false, 529 + "notNull": true, 530 + "autoincrement": false 531 + } 532 + }, 533 + "indexes": { 534 + "users_did_unique": { 535 + "name": "users_did_unique", 536 + "columns": [ 537 + "did" 538 + ], 539 + "isUnique": true 540 + } 541 + }, 542 + "foreignKeys": {}, 543 + "compositePrimaryKeys": {}, 544 + "uniqueConstraints": {}, 545 + "checkConstraints": {} 546 + } 547 + }, 548 + "views": {}, 549 + "enums": {}, 550 + "_meta": { 551 + "schemas": {}, 552 + "tables": {}, 553 + "columns": {} 554 + }, 555 + "internal": { 556 + "indexes": {} 557 + } 558 + }
+7
lib/db/migrations/meta/_journal.json
··· 43 43 "when": 1776761204110, 44 44 "tag": "0005_robust_invaders", 45 45 "breakpoints": true 46 + }, 47 + { 48 + "idx": 6, 49 + "version": "6", 50 + "when": 1776875819748, 51 + "tag": "0006_next_wendell_rand", 52 + "breakpoints": true 46 53 } 47 54 ] 48 55 }
+7 -1
lib/db/schema.ts
··· 91 91 dryRun: integer("dry_run", { mode: "boolean" }).notNull().default(false), 92 92 indexedAt: integer("indexed_at", { mode: "timestamp_ms" }).notNull(), 93 93 }, 94 - (table) => [index("automations_did_idx").on(table.did)], 94 + (table) => [ 95 + index("automations_did_idx").on(table.did), 96 + // Gallery hot path: WHERE active = true ORDER BY indexed_at DESC LIMIT n. 97 + // Lets SQLite walk the index and stop at LIMIT instead of scanning + sorting. 98 + // Lexicon filter (less common) runs as a residual filter on the narrowed set. 99 + index("automations_active_indexed_at_idx").on(table.active, table.indexedAt), 100 + ], 95 101 ); 96 102 97 103 export const deliveryLogs = sqliteTable(