youiyoux 0.1.10 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/chunks/{chunk-HDJ2TOWZ.js → chunk-5I2VHSEN.js} +1 -1
  2. package/dist/chunks/{chunk-T3KNPVQG.js → chunk-DX2NKY2M.js} +4 -4
  3. package/dist/chunks/{chunk-75DBKEAH.js → chunk-EV62TUDV.js} +1119 -619
  4. package/dist/chunks/{chunk-3RP664IP.js → chunk-N5Z5A637.js} +2 -2
  5. package/dist/chunks/{chunk-DWYDHVLZ.js → chunk-NF4OJYKC.js} +106 -66
  6. package/dist/chunks/{chunk-X2YYMHR4.js → chunk-PCLXMWZ2.js} +3 -3
  7. package/dist/chunks/{chunk-OCZJ3JGH.js → chunk-PWGZRP3C.js} +3 -3
  8. package/dist/chunks/{chunk-FLBRX6Y3.js → chunk-XOXF2ILO.js} +1 -1
  9. package/dist/chunks/{chunk-6GITMASZ.js → chunk-ZOQW6LNH.js} +56 -900
  10. package/dist/chunks/chunk-ZWVWENSB.js +899 -0
  11. package/dist/chunks/{src-XTNHB65M.js → src-4QOHR5GC.js} +3 -3
  12. package/dist/chunks/src-624TVB6T.js +55 -0
  13. package/dist/chunks/{src-DPFJ4S2N.js → src-E6ZPFKPR.js} +3 -2
  14. package/dist/chunks/{src-75ODGHE3.js → src-HVYMF4S2.js} +9 -7
  15. package/dist/chunks/{src-BYNWPNRT.js → src-IKDQ2BZO.js} +7 -1
  16. package/dist/chunks/{src-ZG7JU6YJ.js → src-PBM76LFQ.js} +2 -2
  17. package/dist/chunks/{src-6SNQVIZS.js → src-R7JGKOYI.js} +4 -4
  18. package/dist/chunks/{src-PURAHRCR.js → src-YC64NNQ3.js} +2 -2
  19. package/dist/hub/assets/{CandidatePanel-ZzbsyZo3.js → CandidatePanel-DfntneYT.js} +1 -1
  20. package/dist/hub/assets/{CatalogScreen-DDYlvOHy.js → CatalogScreen-kpFYZqX3.js} +1 -1
  21. package/dist/hub/assets/{ChangesScreen-C8_EyHgJ.js → ChangesScreen-Chu-Ui1B.js} +1 -1
  22. package/dist/hub/assets/{CheatSheet-DfcPiXdS.js → CheatSheet-B1dXTYTW.js} +1 -1
  23. package/dist/hub/assets/{CommandPalette-DfFkDTVg.js → CommandPalette-LBZHJ659.js} +1 -1
  24. package/dist/hub/assets/{DiscoverScreen-D6NTgEgk.js → DiscoverScreen-Yd3gVUG4.js} +2 -2
  25. package/dist/hub/assets/DraftEditor-QH9Sa_Uc.js +5 -0
  26. package/dist/hub/assets/{GapsScreen-CqedCj4z.js → GapsScreen-DPYGlNnA.js} +1 -1
  27. package/dist/hub/assets/{HomeScreen-CIj1QTkw.js → HomeScreen-BCPPk7mz.js} +1 -1
  28. package/dist/hub/assets/{InventoryScreen-CyvFXa2_.js → InventoryScreen-SleB733C.js} +1 -1
  29. package/dist/hub/assets/{LibraryScreen-C4WU0f42.js → LibraryScreen-BVXhOVPU.js} +1 -1
  30. package/dist/hub/assets/{Opened-CeRWkA7c.js → Opened-CJyMbxqV.js} +1 -1
  31. package/dist/hub/assets/OrgApp-Dxs_7CxZ.js +1 -0
  32. package/dist/hub/assets/{Preview-CgW-FMB7.js → Preview-DWFP0BIe.js} +1 -1
  33. package/dist/hub/assets/{RuleDetail-BDhZ1gCd.js → RuleDetail-C2fTlLJL.js} +3 -3
  34. package/dist/hub/assets/{RulesScreen-D3rSo_z2.js → RulesScreen-BABeAGJg.js} +3 -3
  35. package/dist/hub/assets/{ScreenImage-DLeA3nRh.js → ScreenImage-CyhKm_nj.js} +1 -1
  36. package/dist/hub/assets/ScreensScreen-8Ih8byGy.js +1 -0
  37. package/dist/hub/assets/{SearchField-DtEgq9-Q.js → SearchField-hF-6-YDM.js} +1 -1
  38. package/dist/hub/assets/{ServiceDown-Bn6AcA3J.js → ServiceDown-S4ya0Cob.js} +1 -1
  39. package/dist/hub/assets/{SpecimenCapture-p6YErF5y.js → SpecimenCapture-e_pgT4Sp.js} +1 -1
  40. package/dist/hub/assets/{Specimens-DuaE0WeO.js → Specimens-D_9hjlJH.js} +2 -2
  41. package/dist/hub/assets/{StackScreen-DHDulDng.js → StackScreen-J2FQilLW.js} +1 -1
  42. package/dist/hub/assets/{StubScreens-CmhVNX_Z.js → StubScreens-DAkMph3i.js} +1 -1
  43. package/dist/hub/assets/StyleScreen-C5WytSZU.js +1 -0
  44. package/dist/hub/assets/{Tabs-Cbh9BCKW.js → Tabs-DU-fFRDV.js} +1 -1
  45. package/dist/hub/assets/{TokenDetail-BICobLK1.js → TokenDetail-CDY1xxb9.js} +1 -1
  46. package/dist/hub/assets/{TokensScreen-CyTHn55q.js → TokensScreen-Sx4aICWk.js} +1 -1
  47. package/dist/hub/assets/{WorkspaceScreen-uPWqfklh.js → WorkspaceScreen-CzZ16fPZ.js} +1 -1
  48. package/dist/hub/assets/{client-BXHiuV_w.js → client-Bdgr12LP.js} +1 -1
  49. package/dist/hub/assets/{discover-CFhyKler.js → discover-va2KFuK_.js} +1 -1
  50. package/dist/hub/assets/{dist-DYNYuTjI.js → dist-C8tf4g8c.js} +1 -1
  51. package/dist/hub/assets/{dist-VmEpVQjP.js → dist-CC_p8DIj.js} +1 -1
  52. package/dist/hub/assets/{dist-C6iTWL0b.js → dist-YTWqg5sS.js} +1 -1
  53. package/dist/hub/assets/{family-tiles-B8_onXUi.js → family-tiles-qaK1-_c8.js} +1 -1
  54. package/dist/hub/assets/main-cvOFSjox.js +2 -0
  55. package/dist/hub/assets/main-pNY4iNRo.css +2 -0
  56. package/dist/hub/assets/{try-live-CX04d2L2.js → try-live-Iys3nCBG.js} +1 -1
  57. package/dist/hub/assets/when-COrv5lkZ.js +1 -0
  58. package/dist/hub/assets/{workflow-DbBdiOmA.js → workflow-C53UBmRZ.js} +1 -1
  59. package/dist/hub/index.html +2 -2
  60. package/dist/hub-previews.js +6 -5
  61. package/dist/hub-server.js +3 -3
  62. package/dist/hub-snapshot.js +1 -1
  63. package/dist/schema/youi.org.schema.json +438 -0
  64. package/dist/schema/youi.record.schema.json +338 -0
  65. package/dist/youi.js +7 -6
  66. package/dist/youiyoux.js +7 -6
  67. package/package.json +4 -4
  68. package/dist/hub/assets/DraftEditor-CUNGYbg0.js +0 -5
  69. package/dist/hub/assets/ScreensScreen-bGuFIyWj.js +0 -1
  70. package/dist/hub/assets/StyleScreen-LwTqfGID.js +0 -1
  71. package/dist/hub/assets/main-7snyIdoi.css +0 -2
  72. package/dist/hub/assets/main-CfbpuT4Y.js +0 -2
@@ -1,14 +1,20 @@
1
1
  import { createRequire as __youiRequire } from "node:module"; import { dirname as __youiDir } from "node:path"; import { fileURLToPath as __youiPath } from "node:url"; const require = __youiRequire(import.meta.url); const __youiDirname = __youiDir(__youiPath(import.meta.url));
2
+ import {
3
+ INDEX_FILE,
4
+ defaultIndexPath,
5
+ nodeId,
6
+ openStore,
7
+ pageInteger,
8
+ searchTerms
9
+ } from "./chunk-ZWVWENSB.js";
2
10
  import {
3
11
  LIBRARY_SOURCE_KINDS,
4
12
  LibraryEntry,
5
- SHAPE_DIMENSIONS,
6
13
  copyleft,
7
14
  external_exports,
8
15
  sha256,
9
- shortHash,
10
16
  writeFileAtomic
11
- } from "./chunk-DWYDHVLZ.js";
17
+ } from "./chunk-NF4OJYKC.js";
12
18
 
13
19
  // ../discover/src/aliases.ts
14
20
  var CONCEPTS = [
@@ -372,852 +378,8 @@ function npmEntry(object, concepts, searchUrl, fetchedAt) {
372
378
 
373
379
  // ../discover/src/store.ts
374
380
  import { readFile, readdir, rm, stat } from "node:fs/promises";
375
- import { homedir as homedir2 } from "node:os";
376
- import { join as join2 } from "node:path";
377
-
378
- // ../store/src/driver.ts
379
- function searchTerms(words2) {
380
- const terms = words2.flatMap((w) => w.normalize("NFC").toLowerCase().split(/[^\p{L}\p{N}]+/u)).filter((w) => w.length > 0);
381
- return [...new Set(terms)].slice(0, 64);
382
- }
383
- function numbered(sql) {
384
- let n = 0;
385
- return sql.replace(/\?/g, () => `$${++n}`);
386
- }
387
- var Lock = class {
388
- tail = Promise.resolve();
389
- run(fn) {
390
- const next = this.tail.then(fn, fn);
391
- this.tail = next.catch(() => {
392
- });
393
- return next;
394
- }
395
- };
396
- function pageInteger(value, fallback, max = 500) {
397
- if (value === void 0 || Number.isNaN(value)) return fallback;
398
- const bounded = Math.max(0, Math.min(max, Math.floor(value)));
399
- return Number.isSafeInteger(bounded) ? bounded : fallback;
400
- }
401
-
402
- // ../store/src/migrations.ts
403
- var MIGRATIONS = [
404
- {
405
- id: 1,
406
- name: "base",
407
- statements: ({ json: json2 }) => [
408
- `CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)`,
409
- // Discover listings: metadata only, never source. scope 'chosen' is the synced sources, 'all' what --all added.
410
- `CREATE TABLE IF NOT EXISTS discover_items (
411
- id TEXT PRIMARY KEY,
412
- scope TEXT NOT NULL,
413
- ref TEXT,
414
- name TEXT NOT NULL,
415
- title TEXT,
416
- summary TEXT NOT NULL,
417
- kind TEXT NOT NULL,
418
- source_kind TEXT,
419
- source_class TEXT NOT NULL,
420
- registry TEXT,
421
- licence TEXT NOT NULL,
422
- style TEXT NOT NULL,
423
- downloads INTEGER,
424
- stars INTEGER,
425
- archived INTEGER NOT NULL,
426
- search_text TEXT NOT NULL,
427
- entry ${json2} NOT NULL,
428
- built_at TEXT NOT NULL
429
- )`,
430
- `CREATE INDEX IF NOT EXISTS discover_items_scope ON discover_items (scope, source_class)`,
431
- `CREATE INDEX IF NOT EXISTS discover_items_ref ON discover_items (ref)`,
432
- `CREATE TABLE IF NOT EXISTS discover_roles (item_id TEXT NOT NULL, role TEXT NOT NULL, PRIMARY KEY (item_id, role))`,
433
- `CREATE INDEX IF NOT EXISTS discover_roles_role ON discover_roles (role)`,
434
- `CREATE TABLE IF NOT EXISTS signals (key TEXT PRIMARY KEY, kind TEXT NOT NULL, data ${json2} NOT NULL, fetched_at TEXT)`,
435
- `CREATE TABLE IF NOT EXISTS licences (item_id TEXT PRIMARY KEY, spdx TEXT NOT NULL, evidence_url TEXT, verified_at TEXT)`,
436
- // Inventory history: one row per project per run.
437
- `CREATE TABLE IF NOT EXISTS scans (
438
- id TEXT PRIMARY KEY,
439
- run TEXT NOT NULL,
440
- source TEXT NOT NULL,
441
- project TEXT NOT NULL,
442
- root TEXT NOT NULL,
443
- commit_sha TEXT,
444
- scanned_at TEXT NOT NULL,
445
- status TEXT NOT NULL,
446
- files INTEGER NOT NULL,
447
- looks INTEGER NOT NULL,
448
- families INTEGER NOT NULL,
449
- near_duplicates INTEGER NOT NULL,
450
- roles ${json2} NOT NULL
451
- )`,
452
- `CREATE INDEX IF NOT EXISTS scans_project ON scans (project, scanned_at)`,
453
- `CREATE INDEX IF NOT EXISTS scans_run ON scans (run)`,
454
- `CREATE TABLE IF NOT EXISTS families (
455
- run TEXT NOT NULL,
456
- id TEXT NOT NULL,
457
- role TEXT NOT NULL,
458
- looks INTEGER NOT NULL,
459
- near_duplicate INTEGER NOT NULL,
460
- projects ${json2} NOT NULL,
461
- kits ${json2} NOT NULL,
462
- shape ${json2} NOT NULL,
463
- PRIMARY KEY (run, id)
464
- )`,
465
- `CREATE TABLE IF NOT EXISTS looks (
466
- run TEXT NOT NULL,
467
- id TEXT NOT NULL,
468
- project TEXT NOT NULL,
469
- role TEXT NOT NULL,
470
- file TEXT NOT NULL,
471
- line INTEGER NOT NULL,
472
- col INTEGER NOT NULL,
473
- tag TEXT NOT NULL,
474
- kit TEXT,
475
- family TEXT,
476
- routes ${json2} NOT NULL,
477
- shape ${json2} NOT NULL,
478
- PRIMARY KEY (run, id)
479
- )`,
480
- `CREATE INDEX IF NOT EXISTS looks_family ON looks (run, family)`,
481
- // The graph: what uses what, read with recursive CTEs that are the same text on both engines.
482
- `CREATE TABLE IF NOT EXISTS nodes (id TEXT PRIMARY KEY, kind TEXT NOT NULL, ref TEXT NOT NULL, source TEXT NOT NULL)`,
483
- `CREATE INDEX IF NOT EXISTS nodes_ref ON nodes (kind, ref)`,
484
- `CREATE TABLE IF NOT EXISTS edges ("from" TEXT NOT NULL, "to" TEXT NOT NULL, kind TEXT NOT NULL, source TEXT NOT NULL, PRIMARY KEY ("from", "to", kind))`,
485
- `CREATE INDEX IF NOT EXISTS edges_to ON edges ("to", kind)`,
486
- `CREATE INDEX IF NOT EXISTS edges_source ON edges (source)`
487
- ]
488
- },
489
- { id: 2, name: "discover-search", statements: (dialect) => dialect.searchDdl },
490
- { id: 3, name: "project-graph", statements: () => [
491
- `ALTER TABLE nodes ADD COLUMN project TEXT`,
492
- `CREATE INDEX nodes_project ON nodes (project, kind, ref)`,
493
- `CREATE INDEX looks_project_family ON looks (run, project, family)`,
494
- // Old graph ids cannot be assigned safely to a project; rebuild restores this cache.
495
- `DELETE FROM edges`,
496
- `DELETE FROM nodes`
497
- ] }
498
- ];
499
- var SCHEMA_VERSION = Math.max(...MIGRATIONS.map((m) => m.id));
500
-
501
- // ../store/src/store.ts
502
- var TABLES = ["meta", "discover_items", "discover_roles", "signals", "licences", "scans", "families", "looks", "nodes", "edges"];
503
- var MAX_PARAMS = 3e4;
504
- var MAX_DEPTH = 6;
505
- var SOURCE_CLASSES = ["official", "community", "npm", "other"];
506
- var json = (value) => JSON.stringify(value);
507
- var parse = (value) => typeof value === "string" ? JSON.parse(value) : value;
508
- var int = (value) => Number(value ?? 0);
509
- var marks = (n) => Array.from({ length: n }, () => "?").join(", ");
510
- var batches = (items) => Array.from({ length: Math.ceil(items.length / MAX_PARAMS) }, (_, i) => items.slice(i * MAX_PARAMS, (i + 1) * MAX_PARAMS));
511
- var SqlStore = class {
512
- db;
513
- lock = new Lock();
514
- constructor(adapter) {
515
- this.db = adapter;
516
- }
517
- get engine() {
518
- return this.db.engine;
519
- }
520
- get location() {
521
- return this.db.location;
522
- }
523
- async checkSchema() {
524
- try {
525
- const rows = await this.db.all(`SELECT MAX(id) AS id FROM migrations`);
526
- if (int(rows[0]?.id) !== SCHEMA_VERSION) throw new Error("schema mismatch");
527
- } catch {
528
- throw new Error("Index schema is outdated: run youiyoux index rebuild");
529
- }
530
- }
531
- /** Apply pending migrations, each in its own transaction. Safe to run on every open. */
532
- async migrate() {
533
- return this.lock.run(async () => {
534
- await this.db.exec(`CREATE TABLE IF NOT EXISTS migrations (id INTEGER PRIMARY KEY, name TEXT NOT NULL, applied_at TEXT NOT NULL)`);
535
- const done = new Set((await this.db.all(`SELECT id FROM migrations`)).map((r) => int(r.id)));
536
- const applied = [];
537
- for (const migration of MIGRATIONS) {
538
- if (done.has(migration.id)) continue;
539
- await this.db.exec(this.db.begin);
540
- try {
541
- for (const statement of migration.statements(this.db)) await this.db.exec(statement);
542
- await this.db.run(`INSERT INTO migrations (id, name, applied_at) VALUES (?, ?, ?)`, [migration.id, migration.name, (/* @__PURE__ */ new Date()).toISOString()]);
543
- await this.db.exec("COMMIT");
544
- } catch (error) {
545
- await this.db.exec("ROLLBACK");
546
- throw error;
547
- }
548
- applied.push(migration.id);
549
- }
550
- for (const statement of this.db.optional ?? []) await this.db.exec(statement).catch(() => {
551
- });
552
- return applied;
553
- });
554
- }
555
- write(fn) {
556
- return this.lock.run(async () => {
557
- await this.db.exec(this.db.begin);
558
- try {
559
- const result = await fn();
560
- await this.db.exec("COMMIT");
561
- return result;
562
- } catch (error) {
563
- await this.db.exec("ROLLBACK").catch(() => {
564
- });
565
- throw error;
566
- }
567
- });
568
- }
569
- /** Rows in batches of one multi-row INSERT each. `columns` are SQL names (quote reserved ones). */
570
- async insert(table, columns, rows, conflict) {
571
- const size = Math.max(1, Math.floor(MAX_PARAMS / columns.length));
572
- for (let i = 0; i < rows.length; i += size) {
573
- const batch = rows.slice(i, i + size);
574
- const values = batch.map(() => `(${marks(columns.length)})`).join(", ");
575
- await this.db.run(`INSERT INTO ${table} (${columns.join(", ")}) VALUES ${values} ${conflict}`, batch.flat());
576
- }
577
- }
578
- async close() {
579
- await this.lock.run(() => this.db.close());
580
- }
581
- async reset() {
582
- await this.write(async () => {
583
- for (const table of TABLES) await this.db.run(`DELETE FROM ${table}`);
584
- });
585
- }
586
- async stats() {
587
- const tables = {};
588
- for (const table of TABLES) tables[table] = int((await this.db.all(`SELECT CAST(COUNT(*) AS INTEGER) AS n FROM ${table}`))[0]?.n);
589
- const schema = int((await this.db.all(`SELECT MAX(id) AS id FROM migrations`))[0]?.id);
590
- return { engine: this.engine, location: this.location, schema: schema || SCHEMA_VERSION, generation: await this.meta("generation"), tables };
591
- }
592
- async meta(key) {
593
- const row = (await this.db.all(`SELECT value FROM meta WHERE key = ?`, [key]))[0];
594
- return row ? row.value : null;
595
- }
596
- async setMeta(key, value) {
597
- await this.write(() => this.db.run(`INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value`, [key, value]));
598
- }
599
- /* ------------------------------------------------------------------ Discover */
600
- async putItems(items) {
601
- const columns = ["id", "scope", "ref", "name", "title", "summary", "kind", "source_kind", "source_class", "registry", "licence", "style", "downloads", "stars", "archived", "search_text", "entry", "built_at"];
602
- const update = columns.slice(1).map((c) => `${c} = excluded.${c}`).join(", ");
603
- await this.insert("discover_items", columns, items.map((i) => [i.id, i.scope, i.ref, i.name, i.title, i.summary, i.kind, i.sourceKind, i.sourceClass, i.registry, i.licence, i.style, i.downloads, i.stars, i.archived ? 1 : 0, i.searchText, json(i.entry), i.builtAt]), `ON CONFLICT (id) DO UPDATE SET ${update}`);
604
- const ids = items.map((i) => i.id);
605
- for (const batch of batches(ids)) await this.db.run(`DELETE FROM discover_roles WHERE item_id IN (${marks(batch.length)})`, batch);
606
- await this.insert("discover_roles", ["item_id", "role"], items.flatMap((i) => [...new Set(i.roles)].map((role) => [i.id, role])), "ON CONFLICT (item_id, role) DO NOTHING");
607
- }
608
- async replaceDiscoverItems(scope, items) {
609
- await this.write(async () => {
610
- await this.db.run(`DELETE FROM discover_roles WHERE item_id IN (SELECT id FROM discover_items WHERE scope = ?)`, [scope]);
611
- await this.db.run(`DELETE FROM discover_items WHERE scope = ?`, [scope]);
612
- await this.putItems(items.map((i) => ({ ...i, scope })));
613
- });
614
- }
615
- async upsertDiscoverItems(items) {
616
- await this.write(() => this.putItems(items));
617
- }
618
- /** WHERE conditions for a filter, on discover_items aliased `d`. */
619
- filterSql(filter) {
620
- const parts = [];
621
- const params = [];
622
- const any = (column, values, lower = false) => {
623
- if (!values?.length) return;
624
- parts.push(`${lower ? `LOWER(${column})` : column} IN (${marks(values.length)})`);
625
- params.push(...values.map((v) => lower ? v.toLowerCase() : v));
626
- };
627
- any("d.scope", filter.scopes.length ? filter.scopes : ["chosen"]);
628
- any("d.source_kind", filter.sourceKinds);
629
- any("d.source_class", filter.sourceClasses);
630
- any("d.registry", filter.registries, true);
631
- any("d.licence", filter.licences, true);
632
- any("d.kind", filter.kinds);
633
- any("d.style", filter.styles);
634
- if (filter.roles?.length) {
635
- parts.push(`d.id IN (SELECT item_id FROM discover_roles WHERE role IN (${marks(filter.roles.length)}))`);
636
- params.push(...filter.roles);
637
- }
638
- return { sql: parts.join(" AND "), params };
639
- }
640
- async matchDiscover(query) {
641
- if (query.terms.length === 0) return [];
642
- const filtered = [query.roles, query.sourceKinds, query.sourceClasses, query.registries, query.licences, query.kinds, query.styles].some((v) => v?.length);
643
- if (!filtered && this.db.top) {
644
- const rows = await this.db.top(query.terms, query.scopes.length ? query.scopes : ["chosen"], SOURCE_CLASSES, pageInteger(query.perClass, 400, 1e4));
645
- return rows.map((r) => ({ id: r.id, sourceClass: r.source_class, entry: parse(r.entry) }));
646
- }
647
- const match = this.db.match(query.terms);
648
- const filter = this.filterSql(query);
649
- const ranked = await this.db.all(
650
- `SELECT id, source_class, r FROM (
651
- SELECT id, source_class, r, ROW_NUMBER() OVER (PARTITION BY source_class ORDER BY r, id) AS n
652
- FROM (SELECT d.id, d.source_class, ${match.rank} AS r FROM ${match.from} WHERE ${match.where} AND ${filter.sql}) matched
653
- ) best WHERE n <= CAST(? AS INTEGER) ORDER BY r, id`,
654
- [...match.rankParams, ...match.whereParams, ...filter.params, pageInteger(query.perClass, 400, 1e4)]
655
- );
656
- const entries = /* @__PURE__ */ new Map();
657
- const ids = ranked.map((r) => r.id);
658
- for (const batch of batches(ids)) {
659
- for (const row of await this.db.all(`SELECT id, entry FROM discover_items WHERE id IN (${marks(batch.length)})`, batch)) entries.set(row.id, row.entry);
660
- }
661
- return ranked.map((r) => ({ id: r.id, sourceClass: r.source_class, entry: parse(entries.get(r.id)) }));
662
- }
663
- async listDiscover(filter, page = {}) {
664
- const where = this.filterSql(filter);
665
- const limit = pageInteger(page.limit, 20);
666
- const offset = pageInteger(page.offset, 0, 2147483647);
667
- const rows = await this.db.all(
668
- `SELECT d.id, d.source_class, d.entry FROM discover_items d WHERE ${where.sql} ORDER BY CASE d.source_class WHEN 'official' THEN 0 ELSE 1 END, COALESCE(d.downloads, -1) DESC, d.id LIMIT ? OFFSET ?`,
669
- [...where.params, limit, offset]
670
- );
671
- return { rows: rows.map((r) => ({ id: r.id, sourceClass: r.source_class, entry: parse(r.entry) })), total: await this.countDiscover(filter) };
672
- }
673
- async countDiscover(filter) {
674
- const where = this.filterSql(filter);
675
- return int((await this.db.all(`SELECT CAST(COUNT(*) AS INTEGER) AS n FROM discover_items d WHERE ${where.sql}`, where.params))[0]?.n);
676
- }
677
- async discoverFacets(filter) {
678
- const where = this.filterSql(filter);
679
- const group = async (column) => {
680
- const rows = await this.db.all(`SELECT ${column} AS k, CAST(COUNT(*) AS INTEGER) AS n FROM discover_items d WHERE ${where.sql} GROUP BY ${column} ORDER BY n DESC, k`, where.params);
681
- return Object.fromEntries(rows.map((r) => [r.k ?? "unknown", int(r.n)]));
682
- };
683
- const roles = await this.db.all(`SELECT r.role AS k, CAST(COUNT(*) AS INTEGER) AS n FROM discover_roles r JOIN discover_items d ON d.id = r.item_id WHERE ${where.sql} GROUP BY r.role ORDER BY n DESC, k`, where.params);
684
- return { role: Object.fromEntries(roles.map((r) => [r.k, int(r.n)])), source: await group("COALESCE(d.registry, d.source_kind)"), licence: await group("d.licence"), style: await group("d.style") };
685
- }
686
- async getDiscoverItem(idOrRef, scopes) {
687
- const scoped = scopes.length ? scopes : ["chosen"];
688
- for (const column of ["id", "ref"]) {
689
- const row = (await this.db.all(`SELECT id, source_class, entry FROM discover_items WHERE ${column} = ? AND scope IN (${marks(scoped.length)}) ORDER BY CASE scope WHEN 'chosen' THEN 0 ELSE 1 END, id LIMIT 1`, [idOrRef, ...scoped]))[0];
690
- if (row) return { id: row.id, sourceClass: row.source_class, entry: parse(row.entry) };
691
- }
692
- return null;
693
- }
694
- async discoverRoles(itemIds) {
695
- const out = {};
696
- for (const batch of batches(itemIds)) {
697
- for (const row of await this.db.all(`SELECT item_id, role FROM discover_roles WHERE item_id IN (${marks(batch.length)}) ORDER BY item_id, role`, batch)) (out[row.item_id] ??= []).push(row.role);
698
- }
699
- return out;
700
- }
701
- /* ------------------------------------------------------- signals and licences */
702
- async replaceSignals(signals) {
703
- await this.write(async () => {
704
- await this.db.run(`DELETE FROM signals`);
705
- await this.insert("signals", ["key", "kind", "data", "fetched_at"], signals.map((s) => [s.key, s.kind, json(s.data), s.fetchedAt]), "ON CONFLICT (key) DO UPDATE SET kind = excluded.kind, data = excluded.data, fetched_at = excluded.fetched_at");
706
- });
707
- }
708
- async signal(key) {
709
- const row = (await this.db.all(`SELECT key, kind, data, fetched_at FROM signals WHERE key = ?`, [key]))[0];
710
- return row ? { key: row.key, kind: row.kind, data: parse(row.data), fetchedAt: row.fetched_at } : null;
711
- }
712
- async replaceLicences(licences) {
713
- await this.write(async () => {
714
- await this.db.run(`DELETE FROM licences`);
715
- await this.insert("licences", ["item_id", "spdx", "evidence_url", "verified_at"], licences.map((l) => [l.itemId, l.spdx, l.evidenceUrl, l.verifiedAt]), "ON CONFLICT (item_id) DO UPDATE SET spdx = excluded.spdx, evidence_url = excluded.evidence_url, verified_at = excluded.verified_at");
716
- });
717
- }
718
- async licence(itemId) {
719
- const row = (await this.db.all(`SELECT item_id, spdx, evidence_url, verified_at FROM licences WHERE item_id = ?`, [itemId]))[0];
720
- return row ? { itemId: row.item_id, spdx: row.spdx, evidenceUrl: row.evidence_url, verifiedAt: row.verified_at } : null;
721
- }
722
- /* ----------------------------------------------------------------- inventory */
723
- async recordInventory(run) {
724
- await this.write(async () => {
725
- await this.insert(
726
- "scans",
727
- ["id", "run", "source", "project", "root", "commit_sha", "scanned_at", "status", "files", "looks", "families", "near_duplicates", "roles"],
728
- run.projects.map((p) => [`${run.run}:${p.project}`, run.run, run.source, p.project, p.root, p.commit, run.scannedAt, p.status, p.files, p.looks, p.families, p.nearDuplicates, json(p.roles)]),
729
- "ON CONFLICT (id) DO UPDATE SET commit_sha = excluded.commit_sha, scanned_at = excluded.scanned_at, status = excluded.status, files = excluded.files, looks = excluded.looks, families = excluded.families, near_duplicates = excluded.near_duplicates, roles = excluded.roles"
730
- );
731
- await this.db.run(`DELETE FROM families WHERE run = ?`, [run.run]);
732
- await this.insert("families", ["run", "id", "role", "looks", "near_duplicate", "projects", "kits", "shape"], run.families.map((f) => [run.run, f.id, f.role, f.looks, f.nearDuplicate ? 1 : 0, json(f.projects), json(f.kits), json(f.shape)]), "ON CONFLICT (run, id) DO NOTHING");
733
- const newest = (await this.db.all(`SELECT MAX(scanned_at) AS scanned_at FROM scans WHERE source = ?`, [run.source]))[0]?.scanned_at;
734
- if (newest && newest > run.scannedAt) return;
735
- await this.db.run(`DELETE FROM looks WHERE run IN (SELECT DISTINCT run FROM scans WHERE source = ?)`, [run.source]);
736
- await this.insert("looks", ["run", "id", "project", "role", "file", "line", "col", "tag", "kit", "family", "routes", "shape"], run.looks.map((l) => [run.run, l.id, l.project, l.role, l.file, l.line, l.column, l.tag, l.kit, l.family, json(l.routes), json(l.shape)]), "ON CONFLICT (run, id) DO NOTHING");
737
- await this.putGraph(run.source, run.graph);
738
- });
739
- }
740
- async history(filter = {}) {
741
- const where = filter.project ? `WHERE project = ?` : "";
742
- const rows = await this.db.all(`SELECT * FROM scans ${where} ORDER BY scanned_at DESC, project LIMIT ? OFFSET ?`, [...filter.project ? [filter.project] : [], pageInteger(filter.limit, 20), pageInteger(filter.offset, 0, 2147483647)]);
743
- return rows.map((r) => ({ id: String(r.id), run: String(r.run), source: String(r.source), project: String(r.project), root: String(r.root), commit: r.commit_sha ?? null, scannedAt: String(r.scanned_at), status: String(r.status), files: int(r.files), looks: int(r.looks), families: int(r.families), nearDuplicates: int(r.near_duplicates), roles: parse(r.roles) }));
744
- }
745
- async latestRun(source) {
746
- const row = (await this.db.all(`SELECT run, source, scanned_at FROM scans ${source ? "WHERE source = ?" : ""} ORDER BY scanned_at DESC, run DESC LIMIT 1`, source ? [source] : []))[0];
747
- return row ? { run: row.run, source: row.source, scannedAt: row.scanned_at } : null;
748
- }
749
- async families(filter = {}) {
750
- const run = filter.run ?? (await this.latestRun())?.run ?? null;
751
- if (!run) return { run: null, total: 0, rows: [] };
752
- const where = `run = ?${filter.role ? " AND role = ?" : ""}`;
753
- const params = [run, ...filter.role ? [filter.role] : []];
754
- const total = int((await this.db.all(`SELECT CAST(COUNT(*) AS INTEGER) AS n FROM families WHERE ${where}`, params))[0]?.n);
755
- const rows = await this.db.all(`SELECT * FROM families WHERE ${where} ORDER BY role, looks DESC, id LIMIT ? OFFSET ?`, [...params, pageInteger(filter.limit, 50), pageInteger(filter.offset, 0, 2147483647)]);
756
- return { run, total, rows: rows.map((r) => ({ id: String(r.id), role: String(r.role), looks: int(r.looks), nearDuplicate: int(r.near_duplicate) === 1, projects: parse(r.projects), kits: parse(r.kits), shape: parse(r.shape) })) };
757
- }
758
- async looks(filter) {
759
- const run = filter.run ?? (await this.latestRun())?.run ?? null;
760
- if (!run) return { run: null, total: 0, rows: [] };
761
- const where = `run = ?${filter.family ? " AND family = ?" : ""}${filter.role ? " AND role = ?" : ""}`;
762
- const params = [run, ...filter.family ? [filter.family] : [], ...filter.role ? [filter.role] : []];
763
- const total = int((await this.db.all(`SELECT CAST(COUNT(*) AS INTEGER) AS n FROM looks WHERE ${where}`, params))[0]?.n);
764
- const rows = await this.db.all(`SELECT * FROM looks WHERE ${where} ORDER BY project, file, line, col, id LIMIT ? OFFSET ?`, [...params, pageInteger(filter.limit, 50), pageInteger(filter.offset, 0, 2147483647)]);
765
- return { run, total, rows: rows.map((r) => ({ id: String(r.id), project: String(r.project), role: String(r.role), file: String(r.file), line: int(r.line), column: int(r.col), tag: String(r.tag), kit: r.kit ?? null, family: r.family ?? null, routes: parse(r.routes), shape: parse(r.shape) })) };
766
- }
767
- /* --------------------------------------------------------------------- graph */
768
- async putGraph(source, graph) {
769
- await this.db.run(`DELETE FROM edges WHERE source = ?`, [source]);
770
- await this.db.run(`DELETE FROM nodes WHERE source = ? AND id NOT IN (SELECT "from" FROM edges) AND id NOT IN (SELECT "to" FROM edges)`, [source]);
771
- const nodes = new Map(graph.nodes.map((n) => [n.id, n]));
772
- await this.insert("nodes", ["id", "kind", "ref", "source", "project"], [...nodes.values()].map((n) => [n.id, n.kind, n.ref, source, n.project ?? null]), "ON CONFLICT (id) DO UPDATE SET kind = excluded.kind, ref = excluded.ref, project = excluded.project");
773
- const edges = new Map(graph.edges.map((e) => [`${e.from}\0${e.to}\0${e.kind}`, e]));
774
- await this.insert("edges", [`"from"`, `"to"`, "kind", "source"], [...edges.values()].map((e) => [e.from, e.to, e.kind, source]), `ON CONFLICT ("from", "to", kind) DO UPDATE SET source = excluded.source`);
775
- }
776
- async replaceGraph(source, graph) {
777
- await this.write(() => this.putGraph(source, graph));
778
- }
779
- async reach(start, kinds, options = {}) {
780
- if (start.length === 0 || kinds.length === 0) return [];
781
- const [near, far] = options.reverse ? [`"to"`, `"from"`] : [`"from"`, `"to"`];
782
- const depth = Math.max(0, Math.min(MAX_DEPTH, options.maxDepth ?? 3));
783
- const rows = await this.db.all(
784
- `WITH RECURSIVE reach(id, depth) AS (
785
- SELECT id, 0 FROM nodes WHERE id IN (${marks(start.length)})
786
- UNION
787
- SELECT e.${far}, r.depth + 1 FROM edges e JOIN reach r ON e.${near} = r.id
788
- WHERE e.kind IN (${marks(kinds.length)}) AND r.depth < CAST(? AS INTEGER)
789
- )
790
- SELECT n.id, n.kind, n.ref, MIN(r.depth) AS depth FROM reach r JOIN nodes n ON n.id = r.id
791
- GROUP BY n.id, n.kind, n.ref ORDER BY MIN(r.depth), n.kind, n.ref`,
792
- [...start, ...kinds, depth]
793
- );
794
- return rows.map((r) => ({ id: r.id, kind: r.kind, ref: r.ref, depth: int(r.depth) }));
795
- }
796
- async findNodes(kind, ref) {
797
- return this.db.all(`SELECT id, kind, ref, source, project FROM nodes WHERE kind = ? AND (ref = ? OR id = ?) ORDER BY id`, [kind, ref, ref]);
798
- }
799
- async nodesOfKind(kind, source) {
800
- return this.db.all(`SELECT id, kind, ref, source, project FROM nodes WHERE kind = ?${source ? " AND source = ?" : ""} ORDER BY id`, source ? [kind, source] : [kind]);
801
- }
802
- async resolveFamily(ref, project) {
803
- const found = (await this.findNodes("family", ref)).filter((n) => project === void 0 || n.project === project);
804
- return found.length > 1 ? { choices: found } : found[0] ?? null;
805
- }
806
- async resolveRule(ref, project) {
807
- const escaped = ref.toLowerCase().replace(/[!%_]/g, "!$&");
808
- const found = await this.db.all(
809
- `SELECT id, kind, ref, source, project FROM nodes WHERE kind = 'rule' AND (LOWER(ref) = ? OR LOWER(id) = ? OR LOWER(ref) LIKE ? ESCAPE '!')${project === void 0 ? "" : " AND project = ?"} ORDER BY id`,
810
- [ref.toLowerCase(), ref.toLowerCase(), `%/rule/${escaped}`, ...project === void 0 ? [] : [project]]
811
- );
812
- return found.length > 1 ? { choices: found } : found[0] ?? null;
813
- }
814
- async familyLooks(source, ids, project) {
815
- const run = (await this.latestRun(source))?.run;
816
- if (!run || ids.length === 0) return /* @__PURE__ */ new Map();
817
- const rows = await this.db.all(`SELECT family AS id, COUNT(*) AS looks, MIN(role) AS role FROM looks WHERE run = ? AND family IN (${marks(ids.length)})${project == null ? "" : " AND project = ?"} GROUP BY family`, [run, ...ids, ...project == null ? [] : [project]]);
818
- return new Map(rows.map((r) => [r.id, { looks: int(r.looks), role: r.role }]));
819
- }
820
- async whereUsed(family, options = {}) {
821
- const node = await this.resolveFamily(family, options.project);
822
- if (!node || "choices" in node) return node;
823
- const used = await this.reach([node.id], ["used-in"], { maxDepth: 2 });
824
- const near = await this.reach([node.id], ["near-duplicate-of"], { maxDepth: 1 });
825
- const info = (await this.familyLooks(node.source, [node.ref], node.project)).get(node.ref);
826
- return {
827
- family: node.ref,
828
- role: info?.role ?? null,
829
- looks: info?.looks ?? 0,
830
- files: used.filter((n) => n.kind === "file").map((n) => n.ref),
831
- routes: used.filter((n) => n.kind === "route").map((n) => n.ref),
832
- nearDuplicates: near.filter((n) => n.depth > 0 && n.kind === "family").map((n) => n.ref),
833
- source: node.source
834
- };
835
- }
836
- async swapImpact(candidate, family, options = {}) {
837
- const node = await this.resolveFamily(family, options.project);
838
- if (!node || "choices" in node) return node;
839
- const cand = (await this.findNodes("candidate", candidate))[0];
840
- const replaces = cand ? await this.reach([cand.id], ["could-replace"], { maxDepth: 1 }) : [];
841
- const twins = (await this.reach([node.id], ["near-duplicate-of"], { maxDepth: 4 })).filter((n) => n.kind === "family");
842
- const used = await this.reach(twins.map((t) => t.id), ["used-in"], { maxDepth: 2 });
843
- const looks = await this.familyLooks(node.source, twins.map((t) => t.ref), node.project);
844
- const others = await this.reach([node.id], ["could-replace"], { maxDepth: 1, reverse: true });
845
- return {
846
- candidate,
847
- family: node.ref,
848
- listed: replaces.some((n) => n.id === node.id),
849
- families: twins.map((t) => ({ id: t.ref, looks: looks.get(t.ref)?.looks ?? 0, depth: t.depth })),
850
- looks: twins.reduce((n, t) => n + (looks.get(t.ref)?.looks ?? 0), 0),
851
- files: used.filter((n) => n.kind === "file").map((n) => n.ref),
852
- routes: used.filter((n) => n.kind === "route").map((n) => n.ref),
853
- otherCandidates: others.filter((n) => n.kind === "candidate" && n.ref !== candidate).map((n) => n.ref)
854
- };
855
- }
856
- async ruleCovers(rule, options = {}) {
857
- const node = await this.resolveRule(rule, options.project);
858
- if (!node || "choices" in node) return node;
859
- const reached = await this.reach([node.id], ["governs", "used-in"], { maxDepth: 3 });
860
- return {
861
- rule: node.ref,
862
- components: reached.filter((n) => n.kind === "component").map((n) => n.ref),
863
- files: reached.filter((n) => n.kind === "file").map((n) => n.ref),
864
- routes: reached.filter((n) => n.kind === "route").map((n) => n.ref)
865
- };
866
- }
867
- };
868
-
869
- // ../store/src/sqlite.ts
870
- import { mkdirSync, statSync } from "node:fs";
871
- import { createRequire } from "node:module";
872
- import { dirname } from "node:path";
873
- var STATEMENT_CACHE = 200;
874
- var sqlite = null;
875
- function openDatabase(...args) {
876
- sqlite ??= createRequire(import.meta.url)("node:sqlite");
877
- return new sqlite.DatabaseSync(...args);
878
- }
879
- var words = (terms) => searchTerms(terms).map((t) => `"${t.replace(/"/g, "")}"`).join(" OR ");
880
- function sqliteAdapter(path, options = {}) {
881
- if (!options.readOnly && path !== ":memory:") mkdirSync(dirname(path), { recursive: true });
882
- const db = openDatabase(path, { timeout: 5e3, readOnly: options.readOnly ?? false });
883
- if (!options.readOnly) db.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL;");
884
- const statements = /* @__PURE__ */ new Map();
885
- const prepare = (sql) => {
886
- let statement = statements.get(sql);
887
- if (!statement) {
888
- if (statements.size >= STATEMENT_CACHE) statements.delete(statements.keys().next().value);
889
- statement = db.prepare(sql);
890
- statements.set(sql, statement);
891
- }
892
- return statement;
893
- };
894
- const values = (params) => params;
895
- return {
896
- engine: "sqlite",
897
- location: path,
898
- json: "TEXT",
899
- begin: "BEGIN IMMEDIATE",
900
- searchDdl: [
901
- // The listing's words, and its scope and source class as tag words (`schosen ccommunity`),
902
- // so an unfiltered search ranks inside the index alone. Kept in step by rowid.
903
- `CREATE VIRTUAL TABLE IF NOT EXISTS discover_fts USING fts5(search_text, tags, tokenize='porter unicode61')`,
904
- `CREATE TRIGGER IF NOT EXISTS discover_items_ai AFTER INSERT ON discover_items BEGIN
905
- INSERT INTO discover_fts (rowid, search_text, tags) VALUES (new.rowid, new.search_text, 's' || new.scope || ' c' || new.source_class);
906
- END`,
907
- `CREATE TRIGGER IF NOT EXISTS discover_items_ad AFTER DELETE ON discover_items BEGIN
908
- DELETE FROM discover_fts WHERE rowid = old.rowid;
909
- END`,
910
- `CREATE TRIGGER IF NOT EXISTS discover_items_au AFTER UPDATE ON discover_items BEGIN
911
- DELETE FROM discover_fts WHERE rowid = old.rowid;
912
- INSERT INTO discover_fts (rowid, search_text, tags) VALUES (new.rowid, new.search_text, 's' || new.scope || ' c' || new.source_class);
913
- END`
914
- ],
915
- match: (terms) => ({
916
- from: `discover_fts JOIN discover_items d ON d.rowid = discover_fts.rowid`,
917
- where: searchTerms(terms).length ? `discover_fts MATCH ?` : `0 = 1 AND ? = ?`,
918
- whereParams: searchTerms(terms).length ? [`search_text : (${words(terms)})`] : [0, 1],
919
- rank: `bm25(discover_fts)`,
920
- rankParams: []
921
- }),
922
- // One query per source class: the tag words narrow it, bm25 ranks only that class's matches, LIMIT keeps the best.
923
- top: async (terms, scopes, classes, perClass) => {
924
- if (!searchTerms(terms).length) return [];
925
- const statement = prepare(`SELECT d.id, d.source_class, d.entry FROM (SELECT rowid AS rid, bm25(discover_fts) AS r FROM discover_fts WHERE discover_fts MATCH ? ORDER BY r LIMIT ?) m JOIN discover_items d ON d.rowid = m.rid ORDER BY m.r, d.id`);
926
- const scope = words(scopes.map((s) => `s${s}`));
927
- return classes.flatMap((c) => statement.all(`search_text : (${words(terms)}) AND tags : (${scope}) AND tags : ${words([`c${c}`])}`, perClass));
928
- },
929
- all: async (sql, params = []) => prepare(sql).all(...values(params)),
930
- run: async (sql, params = []) => {
931
- prepare(sql).run(...values(params));
932
- },
933
- exec: async (script) => db.exec(script),
934
- close: async () => {
935
- statements.clear();
936
- if (db.isOpen) db.close();
937
- }
938
- };
939
- }
940
- async function openSqlite(path, options = {}) {
941
- const connect = async () => {
942
- const store = new SqlStore(sqliteAdapter(path, options));
943
- try {
944
- if (options.readOnly) await store.checkSchema();
945
- else await store.migrate();
946
- return store;
947
- } catch (error) {
948
- await store.close();
949
- throw error;
950
- }
951
- };
952
- const identity = () => {
953
- const s = statSync(path);
954
- return `${s.dev}:${s.ino}`;
955
- };
956
- let inode = null;
957
- if (path !== ":memory:") {
958
- try {
959
- inode = identity();
960
- } catch (error) {
961
- if (error.code !== "ENOENT") throw error;
962
- }
963
- }
964
- let current = await connect();
965
- if (path === ":memory:") return current;
966
- inode ??= identity();
967
- let generation = await current.meta("generation");
968
- let checked = Date.now();
969
- let closed = false;
970
- const lock = new Lock();
971
- return new Proxy(current, {
972
- get(_target, key) {
973
- const value = Reflect.get(current, key);
974
- if (typeof value !== "function") return value;
975
- return (...args) => lock.run(async () => {
976
- if (key === "close") {
977
- closed = true;
978
- return current.close();
979
- }
980
- if (closed) throw new Error("Store is closed");
981
- const next = identity();
982
- const now = Date.now();
983
- const nextGeneration = next === inode && now - checked >= 100 ? await current.meta("generation") : generation;
984
- if (next !== inode || nextGeneration !== generation) {
985
- await current.close();
986
- const openingInode = identity();
987
- current = await connect();
988
- inode = openingInode;
989
- generation = await current.meta("generation");
990
- }
991
- if (now - checked >= 100) checked = now;
992
- return Reflect.apply(Reflect.get(current, key), current, args);
993
- });
994
- }
995
- });
996
- }
997
- function checkpointSqlite(path) {
998
- const db = openDatabase(path);
999
- try {
1000
- const result = db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
1001
- if (Number(result?.busy ?? 0)) throw new Error("Index checkpoint is busy; retry rebuild");
1002
- } finally {
1003
- db.close();
1004
- }
1005
- }
1006
-
1007
- // ../store/src/postgres.ts
1008
- var TRIGRAM_TERMS = 8;
1009
- function postgresAdapter(connection, location) {
1010
- const adapter = {
1011
- engine: "postgres",
1012
- location,
1013
- json: "JSONB",
1014
- begin: "BEGIN",
1015
- trigram: false,
1016
- searchDdl: [
1017
- // English stemming like FTS5's porter, but no stop-word list, so no asked-for word is ever dropped.
1018
- `DO $$ BEGIN
1019
- IF NOT EXISTS (SELECT 1 FROM pg_ts_dict WHERE dictname = 'youi_stem') THEN
1020
- CREATE TEXT SEARCH DICTIONARY youi_stem (TEMPLATE = snowball, Language = english);
1021
- END IF;
1022
- IF NOT EXISTS (SELECT 1 FROM pg_ts_config WHERE cfgname = 'youi') THEN
1023
- CREATE TEXT SEARCH CONFIGURATION youi (COPY = simple);
1024
- ALTER TEXT SEARCH CONFIGURATION youi ALTER MAPPING FOR asciiword, word, numword, asciihword, hword, numhword, hword_asciipart, hword_part, hword_numpart WITH youi_stem;
1025
- END IF;
1026
- END $$`,
1027
- `ALTER TABLE discover_items ADD COLUMN IF NOT EXISTS search_tsv tsvector GENERATED ALWAYS AS (to_tsvector('youi', search_text)) STORED`,
1028
- `CREATE INDEX IF NOT EXISTS discover_items_search ON discover_items USING GIN (search_tsv)`
1029
- ],
1030
- optional: [`CREATE EXTENSION IF NOT EXISTS pg_trgm`, `CREATE INDEX IF NOT EXISTS discover_items_name_trgm ON discover_items USING GIN (name gin_trgm_ops)`],
1031
- match(terms) {
1032
- terms = searchTerms(terms);
1033
- const query = terms.join(" | ");
1034
- const prefixes = adapter.trigram ? terms.slice(0, TRIGRAM_TERMS).map((t) => `${t}%`) : [];
1035
- return {
1036
- from: `discover_items d`,
1037
- where: prefixes.length ? `(d.search_tsv @@ to_tsquery('youi', ?) OR ${prefixes.map(() => "d.name ILIKE ? ESCAPE '!'").join(" OR ")})` : `d.search_tsv @@ to_tsquery('youi', ?)`,
1038
- whereParams: [query, ...prefixes],
1039
- rank: `-ts_rank(d.search_tsv, to_tsquery('youi', ?))`,
1040
- rankParams: [query]
1041
- };
1042
- },
1043
- all: async (sql, params = []) => (await connection.query(numbered(sql), [...params])).rows,
1044
- run: async (sql, params = []) => {
1045
- await connection.query(numbered(sql), [...params]);
1046
- },
1047
- exec: (script) => connection.exec(script),
1048
- close: () => connection.close()
1049
- };
1050
- return adapter;
1051
- }
1052
- async function openWith(connection, location, options = {}) {
1053
- const adapter = postgresAdapter(connection, location);
1054
- const store = new SqlStore(adapter);
1055
- try {
1056
- if (options.readOnly) {
1057
- await connection.exec("SET default_transaction_read_only = on");
1058
- await store.checkSchema();
1059
- } else await store.migrate();
1060
- } catch (error) {
1061
- await store.close();
1062
- throw error;
1063
- }
1064
- adapter.trigram = (await adapter.all(`SELECT 1 AS ok FROM pg_extension WHERE extname = 'pg_trgm'`)).length > 0;
1065
- return store;
1066
- }
1067
- function redactUrl(url) {
1068
- try {
1069
- const parsed = new URL(url);
1070
- if (parsed.password) parsed.password = "***";
1071
- return parsed.toString();
1072
- } catch {
1073
- return "postgres://\u2026";
1074
- }
1075
- }
1076
- async function openPostgres(url, options = {}) {
1077
- const { default: pg } = await import("pg");
1078
- const client = new pg.Client({ connectionString: url });
1079
- await client.connect();
1080
- return openWith({ query: (sql, params) => client.query(sql, params), exec: async (script) => void await client.query(script), close: () => client.end() }, redactUrl(url), options);
1081
- }
1082
-
1083
- // ../store/src/open.ts
1084
381
  import { homedir } from "node:os";
1085
- import { dirname as dirname2, join } from "node:path";
1086
- var INDEX_FILE = "index.db";
1087
- function defaultIndexPath(env = process.env) {
1088
- if (env.YOUI_INDEX) return env.YOUI_INDEX;
1089
- return join(env.XDG_CACHE_HOME || join(homedir(), ".cache"), "youiyoux", INDEX_FILE);
1090
- }
1091
- function indexHome(path, env = process.env) {
1092
- return path === ":memory:" ? dirname2(defaultIndexPath(env)) : dirname2(path);
1093
- }
1094
- function storeLocation(options = {}) {
1095
- const env = options.env ?? process.env;
1096
- return env.DATABASE_URL ? redactUrl(env.DATABASE_URL) : options.path ?? defaultIndexPath(env);
1097
- }
1098
- async function openStore(options = {}) {
1099
- const env = options.env ?? process.env;
1100
- if (env.DATABASE_URL) return openPostgres(env.DATABASE_URL, options);
1101
- return openSqlite(options.path ?? defaultIndexPath(env), options);
1102
- }
1103
-
1104
- // ../store/src/derive.ts
1105
- var nodeId = {
1106
- family: (inventory, id, project = "") => `family:${shortHash(inventory)}/${encodeURIComponent(project)}/${id}`,
1107
- file: (project, file) => `file:${project}:${file}`,
1108
- route: (project, route) => `route:${project}:${route}`,
1109
- project: (id) => `project:${id}`,
1110
- library: (name) => `library:${name}`,
1111
- candidate: (id) => `candidate:${id}`,
1112
- rule: (id, project = "") => `rule:${encodeURIComponent(project)}:${id}`,
1113
- component: (id, project = "") => `component:${encodeURIComponent(project)}:${id}`
1114
- };
1115
- var GraphBuilder = class {
1116
- nodes = /* @__PURE__ */ new Map();
1117
- edges = /* @__PURE__ */ new Map();
1118
- node(node) {
1119
- if (!this.nodes.has(node.id)) this.nodes.set(node.id, node);
1120
- return node.id;
1121
- }
1122
- edge(from, to, kind) {
1123
- if (from !== to) this.edges.set(`${from}\0${to}\0${kind}`, { from, to, kind });
1124
- }
1125
- build() {
1126
- return { nodes: [...this.nodes.values()], edges: [...this.edges.values()] };
1127
- }
1128
- };
1129
- var TOLERANCE_KEY = { height: "height", paddingX: "padding", paddingY: "padding", radius: "radius", fontSize: "fontSize", fontWeight: "fontWeight" };
1130
- function nearDuplicatePairs(families, tolerance) {
1131
- const pairs = [];
1132
- for (let i = 0; i < families.length; i++) {
1133
- for (let j = i + 1; j < families.length; j++) {
1134
- const a = families[i];
1135
- const b = families[j];
1136
- if (a.role !== b.role) continue;
1137
- if (a.kits.length > 0 && b.kits.length > 0 && a.kits.every((k) => b.kits.includes(k)) && b.kits.every((k) => a.kits.includes(k))) continue;
1138
- let compared = 0;
1139
- let close = true;
1140
- for (const dimension of SHAPE_DIMENSIONS) {
1141
- const x = a.shape[dimension];
1142
- const y = b.shape[dimension];
1143
- if (x === null || y === null) continue;
1144
- compared++;
1145
- if (Math.abs(x - y) > 2 * tolerance[TOLERANCE_KEY[dimension]]) close = false;
1146
- }
1147
- if (close && compared >= 2) pairs.push([a.id, b.id]);
1148
- }
1149
- }
1150
- return pairs;
1151
- }
1152
- function inventoryRun(report, source) {
1153
- const run = `${report.generatedAt}#${shortHash(`${source}\0${report.generatedAt}`).slice(0, 8)}`;
1154
- const g = new GraphBuilder();
1155
- const familyNode = (id, project) => g.node({ id: nodeId.family(source, id, project), kind: "family", ref: id, project });
1156
- for (const family of report.families) for (const project of family.projects) familyNode(family.id, project);
1157
- const uses = report.looks.filter((l) => !l.story);
1158
- for (const look of uses) {
1159
- if (!look.family) continue;
1160
- const file = g.node({ id: nodeId.file(look.project, look.file), kind: "file", ref: `${look.project}:${look.file}` });
1161
- g.edge(familyNode(look.family, look.project), file, "used-in");
1162
- for (const route of look.routes) g.edge(file, g.node({ id: nodeId.route(look.project, route), kind: "route", ref: `${look.project}:${route}` }), "used-in");
1163
- }
1164
- for (const [a, b] of nearDuplicatePairs(report.families, report.tolerance)) {
1165
- const projects = report.families.find((f) => f.id === a).projects.filter((p) => report.families.find((f) => f.id === b).projects.includes(p));
1166
- for (const project of projects) {
1167
- g.edge(familyNode(a, project), familyNode(b, project), "near-duplicate-of");
1168
- g.edge(familyNode(b, project), familyNode(a, project), "near-duplicate-of");
1169
- }
1170
- }
1171
- for (const primitive of report.primitives) g.edge(g.node({ id: nodeId.project(primitive.project), kind: "project", ref: primitive.project }), g.node({ id: nodeId.library(primitive.package), kind: "library", ref: primitive.package }), "uses-library");
1172
- for (const kit of report.kits) g.edge(g.node({ id: nodeId.project(kit.project), kind: "project", ref: kit.project }), g.node({ id: nodeId.library(kit.id), kind: "library", ref: kit.id }), "uses-library");
1173
- const roles = (project) => {
1174
- const counts = {};
1175
- for (const look of uses) if (look.project === project) counts[look.role] = (counts[look.role] ?? 0) + 1;
1176
- return counts;
1177
- };
1178
- return {
1179
- run,
1180
- source,
1181
- scannedAt: report.generatedAt,
1182
- projects: report.projects.map((p) => {
1183
- const families = report.families.filter((f) => f.projects.includes(p.id));
1184
- return { project: p.id, root: p.root, commit: p.commit, status: p.status, files: p.files, looks: uses.filter((l) => l.project === p.id).length, families: families.length, nearDuplicates: families.filter((f) => f.nearDuplicate).length, roles: roles(p.id) };
1185
- }),
1186
- families: report.families.map((f) => ({ id: f.id, role: f.role, looks: f.looks, nearDuplicate: f.nearDuplicate, projects: f.projects, kits: f.kits, shape: f.shape })),
1187
- looks: uses.map((l) => ({ id: l.id, project: l.project, role: l.role, file: l.file, line: l.line, column: l.column, tag: l.tag, kit: l.kit, family: l.family, routes: l.routes, shape: l.shape })),
1188
- graph: g.build()
1189
- };
1190
- }
1191
- function strings(value) {
1192
- if (typeof value === "string") return [value];
1193
- if (Array.isArray(value)) return value.flatMap(strings);
1194
- if (value && typeof value === "object") return Object.values(value).flatMap(strings);
1195
- return [];
1196
- }
1197
- function governedComponents(rule, components) {
1198
- if (rule.severity === "off") return [];
1199
- if (rule.check.engine === "youi" && (rule.check.id === "prop-values" || rule.check.id === "lookalike-component")) return components.map((c) => c.id);
1200
- const named = new Set(rule.check.engine === "youi" ? strings(rule.check.options) : []);
1201
- const words2 = `${rule.title} ${rule.statement} ${rule.agent}`;
1202
- return components.filter((c) => named.has(c.id) || new RegExp(`(?:^|[^A-Za-z0-9])<?${c.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:$|[^A-Za-z0-9])`).test(words2)).map((c) => c.id);
1203
- }
1204
- function projectGraph(input) {
1205
- const g = new GraphBuilder();
1206
- const names = new Map(input.components.map((c) => [c.id, c.name]));
1207
- const component = (id) => g.node({ id: nodeId.component(id, input.project), kind: "component", ref: names.get(id) ?? id, project: input.project });
1208
- for (const rule of input.rules) {
1209
- const node = g.node({ id: nodeId.rule(rule.id, input.project), kind: "rule", ref: rule.id, project: input.project });
1210
- for (const id of governedComponents(rule, input.components)) g.edge(node, component(id), "governs");
1211
- }
1212
- for (const site of input.usage?.usages ?? []) {
1213
- const file = g.node({ id: nodeId.file(input.project, site.file), kind: "file", ref: `${input.project}:${site.file}` });
1214
- g.edge(component(site.element), file, "used-in");
1215
- for (const route of site.routes) g.edge(file, g.node({ id: nodeId.route(input.project, route), kind: "route", ref: `${input.project}:${route}` }), "used-in");
1216
- }
1217
- return g.build();
1218
- }
1219
-
1220
- // ../discover/src/store.ts
382
+ import { join } from "node:path";
1221
383
  var INDEX_FILE2 = "index.json";
1222
384
  var ALL_INDEX_FILE = "index-all.json";
1223
385
  var HEALTH_FILE = "health.json";
@@ -1227,12 +389,12 @@ var BODIES_DIR = "bodies";
1227
389
  function discoverCacheDir(explicit, env = process.env) {
1228
390
  if (explicit) return explicit;
1229
391
  if (env.YOUI_DISCOVER_CACHE) return env.YOUI_DISCOVER_CACHE;
1230
- return join2(env.XDG_CACHE_HOME || join2(homedir2(), ".cache"), "youiyoux", "discover");
392
+ return join(env.XDG_CACHE_HOME || join(homedir(), ".cache"), "youiyoux", "discover");
1231
393
  }
1232
394
  function discoverIndexPath(explicit, env = process.env) {
1233
- if (explicit) return join2(explicit, INDEX_FILE);
395
+ if (explicit) return join(explicit, INDEX_FILE);
1234
396
  if (env.YOUI_INDEX) return env.YOUI_INDEX;
1235
- if (env.YOUI_DISCOVER_CACHE) return join2(env.YOUI_DISCOVER_CACHE, INDEX_FILE);
397
+ if (env.YOUI_DISCOVER_CACHE) return join(env.YOUI_DISCOVER_CACHE, INDEX_FILE);
1236
398
  return defaultIndexPath(env);
1237
399
  }
1238
400
  var Iso = external_exports.string().datetime({ offset: true });
@@ -1279,7 +441,7 @@ var MissingIndexError = class extends Error {
1279
441
  }
1280
442
  };
1281
443
  async function readIndex(cacheDir) {
1282
- const raw = await readJson(join2(cacheDir, INDEX_FILE2));
444
+ const raw = await readJson(join(cacheDir, INDEX_FILE2));
1283
445
  if (raw === null) return null;
1284
446
  const modern = raw.format === 2;
1285
447
  const index = DiscoverIndex.parse(modern ? raw : cleanData(raw));
@@ -1293,11 +455,11 @@ async function readIndex(cacheDir) {
1293
455
  }
1294
456
  var loaded = /* @__PURE__ */ new Map();
1295
457
  async function readAllIndex(cacheDir) {
1296
- const raw = await readJson(join2(cacheDir, ALL_INDEX_FILE));
458
+ const raw = await readJson(join(cacheDir, ALL_INDEX_FILE));
1297
459
  return raw === null ? null : DiscoverIndex.parse(cleanData(raw));
1298
460
  }
1299
461
  async function loadIndex(cacheDir) {
1300
- const file = join2(cacheDir, INDEX_FILE2);
462
+ const file = join(cacheDir, INDEX_FILE2);
1301
463
  const mtimeMs = await stat(file).then((s) => s.mtimeMs, () => null);
1302
464
  if (mtimeMs === null) throw new MissingIndexError(cacheDir);
1303
465
  const hit = loaded.get(file);
@@ -1308,14 +470,14 @@ async function loadIndex(cacheDir) {
1308
470
  return index;
1309
471
  }
1310
472
  async function readSignals(cacheDir) {
1311
- const raw = await readJson(join2(cacheDir, SIGNALS_FILE));
473
+ const raw = await readJson(join(cacheDir, SIGNALS_FILE));
1312
474
  return { npm: { ...raw?.npm }, github: { ...raw?.github }, registryRepos: { ...raw?.registryRepos } };
1313
475
  }
1314
476
  async function readHttpCache(cacheDir) {
1315
477
  const cache = /* @__PURE__ */ new Map();
1316
- const names = await readdir(join2(cacheDir, "listings")).catch(() => []);
478
+ const names = await readdir(join(cacheDir, "listings")).catch(() => []);
1317
479
  await Promise.all(names.map(async (name) => {
1318
- const saved = await readJson(join2(cacheDir, "listings", name));
480
+ const saved = await readJson(join(cacheDir, "listings", name));
1319
481
  if (saved?.version !== 2 || typeof saved.url !== "string" || saved.data === void 0) return;
1320
482
  const body = JSON.stringify(saved.data);
1321
483
  cache.set(saved.url, { ...saved, body, sha256: sha256(body) });
@@ -1374,26 +536,26 @@ async function writeHttpCache(cacheDir, cache) {
1374
536
  continue;
1375
537
  }
1376
538
  keep.add(`${sha256(url)}.json`);
1377
- await writeJson(join2(cacheDir, "listings", `${sha256(url)}.json`), { version: 2, url, etag: entry.etag, lastModified: entry.lastModified, fetchedAt: entry.fetchedAt, data });
539
+ await writeJson(join(cacheDir, "listings", `${sha256(url)}.json`), { version: 2, url, etag: entry.etag, lastModified: entry.lastModified, fetchedAt: entry.fetchedAt, data });
1378
540
  }
1379
- for (const name of await readdir(join2(cacheDir, "listings")).catch(() => [])) if (!keep.has(name)) await rm(join2(cacheDir, "listings", name), { force: true });
1380
- await rm(join2(cacheDir, BODIES_DIR), { recursive: true, force: true });
1381
- await rm(join2(cacheDir, HTTP_FILE), { force: true });
541
+ for (const name of await readdir(join(cacheDir, "listings")).catch(() => [])) if (!keep.has(name)) await rm(join(cacheDir, "listings", name), { force: true });
542
+ await rm(join(cacheDir, BODIES_DIR), { recursive: true, force: true });
543
+ await rm(join(cacheDir, HTTP_FILE), { force: true });
1382
544
  }
1383
545
  async function readSources(cacheDir) {
1384
- const value = await readJson(join2(cacheDir, "sources.json"));
546
+ const value = await readJson(join(cacheDir, "sources.json"));
1385
547
  return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [...CURATED];
1386
548
  }
1387
549
  async function writeSources(cacheDir, names) {
1388
- await writeJson(join2(cacheDir, "sources.json"), [...new Set(names)].sort());
550
+ await writeJson(join(cacheDir, "sources.json"), [...new Set(names)].sort());
1389
551
  }
1390
552
  async function catalogBytes(cacheDir) {
1391
553
  const files = await readdir(cacheDir, { recursive: true, withFileTypes: true });
1392
- const sizes = await Promise.all(files.filter((f) => f.isFile() && !f.parentPath.includes(join2(cacheDir, "source")) && !f.name.startsWith(INDEX_FILE)).map((f) => stat(join2(f.parentPath, f.name)).then((s) => s.size)));
554
+ const sizes = await Promise.all(files.filter((f) => f.isFile() && !f.parentPath.includes(join(cacheDir, "source")) && !f.name.startsWith(INDEX_FILE)).map((f) => stat(join(f.parentPath, f.name)).then((s) => s.size)));
1393
555
  return sizes.reduce((a, b) => a + b, 0);
1394
556
  }
1395
557
  async function readHealth(cacheDir) {
1396
- return await readJson(join2(cacheDir, HEALTH_FILE));
558
+ return await readJson(join(cacheDir, HEALTH_FILE));
1397
559
  }
1398
560
 
1399
561
  // ../discover/src/http.ts
@@ -2016,7 +1178,7 @@ function applySignals(entry, signals) {
2016
1178
  }
2017
1179
 
2018
1180
  // ../discover/src/sync.ts
2019
- import { join as join3 } from "node:path";
1181
+ import { join as join2 } from "node:path";
2020
1182
  function status(id, url, result, items, health = null) {
2021
1183
  if (!result.ok) return { id, url, outcome: "failed", reason: result.reason === "http" ? result.message : `${result.reason}: ${result.message}`, httpStatus: result.status, items: 0, health };
2022
1184
  return { id, url, outcome: result.result.revalidated ? "revalidated" : "fetched", reason: null, httpStatus: result.result.status, items, health };
@@ -2120,7 +1282,7 @@ async function syncIndex(options) {
2120
1282
  } else sources.push(official.ok ? { id: "shadcn", url: SHADCN.registry, outcome: "failed", reason: "not a registry", httpStatus: official.result.status, items: 0, health: null } : status("shadcn", SHADCN.registry, official, 0));
2121
1283
  const listed = directory2.ok ? parseDirectory(directory2.value).registries : [];
2122
1284
  sources.push(status("directory", SHADCN.directory, directory2, listed.length));
2123
- await writeJson(join3(options.cacheDir, "directory.json"), listed);
1285
+ await writeJson(join2(options.cacheDir, "directory.json"), listed);
2124
1286
  const selected = await readSources(options.cacheDir);
2125
1287
  const toFetch = [];
2126
1288
  const previousHealth = await readHealth(options.cacheDir);
@@ -2184,10 +1346,10 @@ async function syncIndex(options) {
2184
1346
  const { signals, report } = await refreshSignals(unique, homes, await readSignals(options.cacheDir), client, now(), { ...defaultBudgets(Boolean(options.token)), ...options.budgets });
2185
1347
  const final = unique.map((entry) => applySignals(entry, signals)).filter((entry) => LibraryEntry.safeParse(entry).success);
2186
1348
  const index = cleanData({ schema: "youi.discover-index/v1", format: 2, builtAt, sources, categories: Object.fromEntries(categories), entries: final });
2187
- await writeJson(join3(options.cacheDir, SIGNALS_FILE), signals);
2188
- await writeJson(join3(options.cacheDir, HEALTH_FILE), cleanData({ checkedAt: builtAt, sources }));
1349
+ await writeJson(join2(options.cacheDir, SIGNALS_FILE), signals);
1350
+ await writeJson(join2(options.cacheDir, HEALTH_FILE), cleanData({ checkedAt: builtAt, sources }));
2189
1351
  await writeHttpCache(options.cacheDir, client.cache);
2190
- await writeJson(join3(options.cacheDir, INDEX_FILE2), index);
1352
+ await writeJson(join2(options.cacheDir, INDEX_FILE2), index);
2191
1353
  const registryStatuses = sources.filter((s) => s.health !== null);
2192
1354
  const known = final.filter((e) => e.license && e.license !== "unknown").length;
2193
1355
  return cleanData({
@@ -2211,9 +1373,9 @@ async function syncIndex(options) {
2211
1373
 
2212
1374
  // ../discover/src/on-demand.ts
2213
1375
  import { readFile as readFile2 } from "node:fs/promises";
2214
- import { join as join4 } from "node:path";
1376
+ import { join as join3 } from "node:path";
2215
1377
  async function directory(cacheDir) {
2216
- return JSON.parse(await readFile2(join4(cacheDir, "directory.json"), "utf8"));
1378
+ return JSON.parse(await readFile2(join3(cacheDir, "directory.json"), "utf8"));
2217
1379
  }
2218
1380
  async function selectSources(cacheDir, action = "list", name) {
2219
1381
  const selected = await readSources(cacheDir);
@@ -2250,7 +1412,7 @@ async function expandAll(cacheDir, options = {}) {
2250
1412
  const entries = dedupe([...index.entries, ...extra.flat()]).entries.filter((e) => !chosen.has(e.id));
2251
1413
  const next = `${JSON.stringify(cleanData({ schema: index.schema, format: 2, builtAt: index.builtAt, sources: [], categories: index.categories, entries }))}
2252
1414
  `;
2253
- if (await readFile2(join4(cacheDir, ALL_INDEX_FILE), "utf8").catch(() => null) !== next) await writeFileAtomic(join4(cacheDir, ALL_INDEX_FILE), next);
1415
+ if (await readFile2(join3(cacheDir, ALL_INDEX_FILE), "utf8").catch(() => null) !== next) await writeFileAtomic(join3(cacheDir, ALL_INDEX_FILE), next);
2254
1416
  }
2255
1417
  async function findListing(cacheDir, id) {
2256
1418
  try {
@@ -2275,7 +1437,7 @@ async function candidateSource(cacheDir, id, options = {}) {
2275
1437
  const template = registry === SHADCN.name ? "https://ui.shadcn.com/r/styles/new-york-v4/{name}.json" : (await directory(cacheDir)).find((r) => r.name === registry)?.template;
2276
1438
  if (!template) throw new Error("Registry URL is unavailable; run youiyoux discover sync");
2277
1439
  const url = registryUrl(template, name);
2278
- const file = join4(cacheDir, "source", `${sha256(url)}.json`);
1440
+ const file = join3(cacheDir, "source", `${sha256(url)}.json`);
2279
1441
  try {
2280
1442
  return JSON.parse(await readFile2(file, "utf8"));
2281
1443
  } catch {
@@ -2292,7 +1454,7 @@ async function candidateSource(cacheDir, id, options = {}) {
2292
1454
 
2293
1455
  // ../discover/src/catalog.ts
2294
1456
  import { stat as stat2 } from "node:fs/promises";
2295
- import { join as join5 } from "node:path";
1457
+ import { join as join4 } from "node:path";
2296
1458
  var CANDIDATES_PER_CLASS = 400;
2297
1459
  var LINKS_PER_ROLE = 10;
2298
1460
  var FINGERPRINT = "discover.fingerprint";
@@ -2336,7 +1498,7 @@ function itemOf(entry, scope, builtAt) {
2336
1498
  };
2337
1499
  }
2338
1500
  async function fingerprint(cacheDir, file) {
2339
- const at = await stat2(join5(cacheDir, file)).then((s) => `${s.size}:${Math.round(s.mtimeMs)}`, () => "-");
1501
+ const at = await stat2(join4(cacheDir, file)).then((s) => `${s.size}:${Math.round(s.mtimeMs)}`, () => "-");
2340
1502
  return JSON.stringify([cacheDir, at]);
2341
1503
  }
2342
1504
  function signalRows(signals) {
@@ -2382,7 +1544,7 @@ async function closeSharedStores() {
2382
1544
  await Promise.all(stores.map(async (s) => (await s.catch(() => null))?.close()));
2383
1545
  }
2384
1546
  async function openCatalog(cacheDir, path, env = process.env) {
2385
- if (!await stat2(join5(cacheDir, INDEX_FILE2)).then((s) => s.isFile(), () => false)) throw new MissingIndexError(cacheDir);
1547
+ if (!await stat2(join4(cacheDir, INDEX_FILE2)).then((s) => s.isFile(), () => false)) throw new MissingIndexError(cacheDir);
2386
1548
  const store = await sharedStore(path, env);
2387
1549
  const key = `${path}\0${cacheDir}`;
2388
1550
  while (refreshing.has(key)) await refreshing.get(key);
@@ -2426,9 +1588,9 @@ function needTerms(need) {
2426
1588
  const read = readNeed(cleanText(need));
2427
1589
  const phrases = [...read.concepts.flatMap((c) => [...c.names, ...c.packages]), ...read.words].map(normalize);
2428
1590
  return searchTerms(phrases.flatMap((phrase) => {
2429
- const words2 = phrase.split(" ").filter(Boolean);
2430
- const specific = words2.filter((w) => !GENERIC.has(w));
2431
- return specific.length ? specific : words2;
1591
+ const words = phrase.split(" ").filter(Boolean);
1592
+ const specific = words.filter((w) => !GENERIC.has(w));
1593
+ return specific.length ? specific : words;
2432
1594
  }));
2433
1595
  }
2434
1596
  async function searchCatalog(store, need, options = {}) {
@@ -2494,7 +1656,7 @@ async function linkCandidates(store) {
2494
1656
 
2495
1657
  // ../discover/src/preview.ts
2496
1658
  import { lstat, rm as rm2 } from "node:fs/promises";
2497
- import { join as join6, posix } from "node:path";
1659
+ import { join as join5, posix } from "node:path";
2498
1660
  var PREVIEW_DIR = "preview";
2499
1661
  var MAX_FETCHED_PARTS = 3;
2500
1662
  function safeRelative(path) {
@@ -2508,7 +1670,7 @@ function safeRelative(path) {
2508
1670
  async function assertNoLinks(base, rel) {
2509
1671
  let at = base;
2510
1672
  for (const part of ["", ...rel.split("/")]) {
2511
- at = part ? join6(at, part) : at;
1673
+ at = part ? join5(at, part) : at;
2512
1674
  const info = await lstat(at).catch(() => null);
2513
1675
  if (!info) return;
2514
1676
  if (info.isSymbolicLink()) throw new Error(`refused: ${rel} leads through a symlink in the preview cache`);
@@ -2519,8 +1681,8 @@ function exportNames(source) {
2519
1681
  for (const match of source.matchAll(/\bexport\s+(?:declare\s+)?(?:async\s+)?(?:function\*?|const|let|var|class)\s+([A-Za-z_$][\w$]*)/g)) names.add(match[1]);
2520
1682
  for (const match of source.matchAll(/\bexport\s+(?!type\b)\{([^}]*)\}(?!\s*from)/g)) {
2521
1683
  for (const item of match[1].split(",")) {
2522
- const words2 = item.trim().split(/\s+/).filter((w) => w !== "type");
2523
- const name = words2.length >= 3 && words2[1] === "as" ? words2[2] : words2[0];
1684
+ const words = item.trim().split(/\s+/).filter((w) => w !== "type");
1685
+ const name = words.length >= 3 && words[1] === "as" ? words[2] : words[0];
2524
1686
  if (name && /^[A-Za-z_$][\w$]*$/.test(name) && !/^type\s/.test(item.trim())) names.add(name);
2525
1687
  }
2526
1688
  }
@@ -2536,8 +1698,8 @@ function importedNames(source, spec) {
2536
1698
  if (/\*\s+as\s+/.test(clause)) names.push("*");
2537
1699
  const braces = /\{([^}]*)\}/.exec(clause);
2538
1700
  if (braces) for (const item of braces[1].split(",")) {
2539
- const words2 = item.trim().split(/\s+/).filter((w) => w && w !== "type");
2540
- if (words2[0]) names.push(words2[0]);
1701
+ const words = item.trim().split(/\s+/).filter((w) => w && w !== "type");
1702
+ if (words[0]) names.push(words[0]);
2541
1703
  }
2542
1704
  const head = clause.replace(/\{[^}]*\}/, "").replace(/\*\s+as\s+\w+/, "").replace(/,/g, " ").trim();
2543
1705
  if (head && head !== "type") names.push("default");
@@ -2559,8 +1721,8 @@ async function preparePreview(cacheDir, id, options = {}) {
2559
1721
  const registry = ref.slice(0, ref.indexOf("/"));
2560
1722
  const source = await candidateSource(cacheDir, id, options);
2561
1723
  const key = sha256(source.url).slice(0, 16);
2562
- const root = join6(cacheDir, PREVIEW_DIR);
2563
- const dir = join6(root, key);
1724
+ const root = join5(cacheDir, PREVIEW_DIR);
1725
+ const dir = join5(root, key);
2564
1726
  await assertNoLinks(cacheDir, `${PREVIEW_DIR}/${key}`);
2565
1727
  await rm2(dir, { recursive: true, force: true });
2566
1728
  const texts = /* @__PURE__ */ new Map();
@@ -2641,7 +1803,7 @@ async function preparePreview(cacheDir, id, options = {}) {
2641
1803
  }
2642
1804
  for (const [file, text] of rewritten) {
2643
1805
  await assertNoLinks(dir, file);
2644
- await writeFileAtomic(join6(dir, file), text);
1806
+ await writeFileAtomic(join5(dir, file), text);
2645
1807
  }
2646
1808
  const packages = [...new Set([...rewritten.values()].flatMap((t) => importedPackages(t)))].sort();
2647
1809
  const declared = [...new Set([...source.dependencies ?? entry.install?.npm ?? [], ...partDeps].map(packageName))].sort();
@@ -2665,17 +1827,17 @@ async function preparePreview(cacheDir, id, options = {}) {
2665
1827
 
2666
1828
  // ../discover/src/gaps.ts
2667
1829
  import { existsSync, readFileSync } from "node:fs";
2668
- import { join as join7 } from "node:path";
1830
+ import { join as join6 } from "node:path";
2669
1831
  var STANDARD_KIT = ["button", "input", "card", "badge", "dialog", "alert", "drawer", "menu", "popover", "tooltip", "tabs", "avatar", "accordion", "table", "carousel", "slider", "switch", "checkbox", "radio", "select", "otp", "progress", "skeleton", "spinner", "sidebar", "navigation", "breadcrumb", "pagination", "command", "toast", "date-picker", "combobox", "chart"];
2670
1832
  var ONLY_EXACT = /* @__PURE__ */ new Set(["hint", "tile", "range", "loading", "picker", "tree", "graph", "plot", "editor", "cta", "notice", "steps", "tag", "toggle", "meter", "command", "banner", "popup", "pager", "loader", "navigation", "chip", "pill"]);
2671
1833
  var ROLE_CONCEPT2 = { button: "button", "link-as-button": "button", input: "input", card: "card", badge: "badge", dialog: "dialog", tabs: "tabs" };
2672
1834
  var CANDIDATES = 3;
2673
1835
  var DEFINITIONS_SHOWN = 5;
2674
1836
  function conceptOfName(name) {
2675
- const words2 = normalize(name.replace(/\.[a-z]+$/, ""));
2676
- if (!words2) return void 0;
1837
+ const words = normalize(name.replace(/\.[a-z]+$/, ""));
1838
+ if (!words) return void 0;
2677
1839
  const phrases = CONCEPTS.flatMap((concept) => concept.names.map((n) => ({ concept, name: normalize(n) }))).sort((a, b) => b.name.length - a.name.length);
2678
- return phrases.find((p) => words2 === p.name || !ONLY_EXACT.has(p.name) && ` ${words2}`.endsWith(` ${p.name}`))?.concept;
1840
+ return phrases.find((p) => words === p.name || !ONLY_EXACT.has(p.name) && ` ${words}`.endsWith(` ${p.name}`))?.concept;
2679
1841
  }
2680
1842
  function conceptOfPackage(pkg) {
2681
1843
  const listed = CONCEPTS.find((c) => c.packages.includes(pkg));
@@ -2686,7 +1848,7 @@ function conceptOfPackage(pkg) {
2686
1848
  function declaredPackages(roots) {
2687
1849
  const found = {};
2688
1850
  for (const [id, root] of roots) {
2689
- const file = join7(root, "package.json");
1851
+ const file = join6(root, "package.json");
2690
1852
  if (!existsSync(file)) continue;
2691
1853
  try {
2692
1854
  const pkg = JSON.parse(readFileSync(file, "utf8"));
@@ -2827,12 +1989,6 @@ export {
2827
1989
  MIN_WEEKLY_DOWNLOADS,
2828
1990
  npmCandidates,
2829
1991
  npmEntry,
2830
- openSqlite,
2831
- checkpointSqlite,
2832
- indexHome,
2833
- storeLocation,
2834
- inventoryRun,
2835
- projectGraph,
2836
1992
  INDEX_FILE2 as INDEX_FILE,
2837
1993
  ALL_INDEX_FILE,
2838
1994
  HEALTH_FILE,