@promptowl/contextnest-community 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,12 @@
1
1
  import {
2
- ANON_USER_ID,
3
2
  config,
4
- getDb
5
- } from "./chunk-RMU3LOPH.js";
3
+ getDb,
4
+ insertOrReplace,
5
+ nowExpr
6
+ } from "./chunk-7V33Z6CS.js";
7
+ import {
8
+ ANON_USER_ID
9
+ } from "./chunk-SLTQACJW.js";
6
10
 
7
11
  // src/governance/stewardship-service.ts
8
12
  import { v4 as uuid2 } from "uuid";
@@ -148,7 +152,6 @@ import {
148
152
  } from "@promptowl/contextnest-engine";
149
153
 
150
154
  // src/nests/service.ts
151
- import { join as join3 } from "path";
152
155
  import { rmSync as rmSync2, mkdirSync as mkdirSync2 } from "fs";
153
156
  import { v4 as uuid } from "uuid";
154
157
  import { NestStorage } from "@promptowl/contextnest-engine";
@@ -188,54 +191,59 @@ function discardImportDir(dest) {
188
191
  }
189
192
  }
190
193
 
194
+ // src/shared/paths.ts
195
+ import { join as join3 } from "path";
196
+ function nestStorageRoot() {
197
+ return config.NEST_STORAGE_ROOT;
198
+ }
199
+ function resolveNestPath(nestId) {
200
+ return join3(nestStorageRoot(), nestId);
201
+ }
202
+
191
203
  // src/telemetry/tracker.ts
192
204
  import { createHash } from "crypto";
193
205
  function computeBatchId(ids) {
194
206
  const key = ids.slice().sort((a, b) => a - b).join(",");
195
207
  return createHash("sha256").update(key).digest("hex").slice(0, 32);
196
208
  }
197
- function trackEvent(event, data) {
209
+ async function trackEvent(event, data) {
198
210
  if (!config.TELEMETRY_ENABLED) return;
199
211
  try {
200
212
  const db = getDb();
201
- db.prepare(
202
- "INSERT INTO telemetry_events (event, data_json) VALUES (?, ?)"
203
- ).run(event, data ? JSON.stringify(data) : null);
213
+ await db.run(
214
+ "INSERT INTO telemetry_events (event, data_json) VALUES (?, ?)",
215
+ [event, data ? JSON.stringify(data) : null]
216
+ );
204
217
  } catch {
205
218
  }
206
219
  }
207
220
  async function flushTelemetry() {
208
221
  if (!config.TELEMETRY_ENABLED || !config.PROMPTOWL_KEY) return;
209
222
  const db = getDb();
210
- const userCount = db.prepare("SELECT COUNT(*) as c FROM users").get()?.c || 0;
211
- const nestCount = db.prepare("SELECT COUNT(*) as c FROM nests").get()?.c || 0;
212
- const events = db.prepare(
223
+ const userCount = (await db.get("SELECT COUNT(*) as c FROM users"))?.c || 0;
224
+ const nestCount = (await db.get("SELECT COUNT(*) as c FROM nests"))?.c || 0;
225
+ const events = await db.all(
213
226
  "SELECT id, event, data_json, created_at FROM telemetry_events WHERE sent = 0 ORDER BY id LIMIT 100"
214
- ).all();
227
+ );
215
228
  if (events.length === 0 && userCount === 0) {
216
229
  console.log("[telemetry] skip flush: no events and no users");
217
230
  return;
218
231
  }
219
- const emailStmt = db.prepare("SELECT email FROM users WHERE id = ?");
220
232
  const emailCache = /* @__PURE__ */ new Map();
221
- const resolveEmail = (userId) => {
233
+ const resolveEmail = async (userId) => {
222
234
  if (typeof userId !== "string" || !userId) return null;
223
235
  if (emailCache.has(userId)) return emailCache.get(userId) ?? null;
224
- const row = emailStmt.get(userId);
236
+ const row = await db.get("SELECT email FROM users WHERE id = ?", [userId]);
225
237
  const email = row?.email ?? null;
226
238
  emailCache.set(userId, email);
227
239
  return email;
228
240
  };
229
241
  const batchId = events.length > 0 ? computeBatchId(events.map((e) => e.id)) : null;
230
- const payload = {
231
- server_key: config.PROMPTOWL_KEY,
232
- batch_id: batchId,
233
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
234
- stats: { users: userCount, nests: nestCount },
235
- events: events.map((e) => {
242
+ const eventPayload = await Promise.all(
243
+ events.map(async (e) => {
236
244
  const data = e.data_json ? JSON.parse(e.data_json) : null;
237
245
  if (data && data.userId && !data.userEmail) {
238
- const email = resolveEmail(data.userId);
246
+ const email = await resolveEmail(data.userId);
239
247
  if (email) data.userEmail = email;
240
248
  }
241
249
  return {
@@ -244,6 +252,13 @@ async function flushTelemetry() {
244
252
  at: e.created_at
245
253
  };
246
254
  })
255
+ );
256
+ const payload = {
257
+ server_key: config.PROMPTOWL_KEY,
258
+ batch_id: batchId,
259
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
260
+ stats: { users: userCount, nests: nestCount },
261
+ events: eventPayload
247
262
  };
248
263
  const promptowlUrl = config.PROMPTOWL_API_URL.replace(/\/$/, "");
249
264
  const url = `${promptowlUrl}/api/telemetry/ingest`;
@@ -260,9 +275,10 @@ async function flushTelemetry() {
260
275
  console.log(`[telemetry] response: ${res.status} ${res.statusText}`);
261
276
  if (res.ok && events.length > 0) {
262
277
  const ids = events.map((e) => e.id);
263
- db.prepare(
264
- `UPDATE telemetry_events SET sent = 1 WHERE id IN (${ids.map(() => "?").join(",")})`
265
- ).run(...ids);
278
+ await db.run(
279
+ `UPDATE telemetry_events SET sent = 1 WHERE id IN (${ids.map(() => "?").join(",")})`,
280
+ ids
281
+ );
266
282
  console.log(`[telemetry] marked ${ids.length} events as sent`);
267
283
  }
268
284
  } catch (err) {
@@ -291,9 +307,12 @@ function isLicenseAdminEmail(email) {
291
307
  if (!lic?.valid || !lic.ownerEmail) return false;
292
308
  return lic.ownerEmail.toLowerCase() === email.toLowerCase();
293
309
  }
294
- function isLicenseAdminUserId(userId) {
310
+ async function isLicenseAdminUserId(userId) {
295
311
  try {
296
- const row = getDb().prepare("SELECT email FROM users WHERE id = ?").get(userId);
312
+ const row = await getDb().get(
313
+ "SELECT email FROM users WHERE id = ?",
314
+ [userId]
315
+ );
297
316
  return isLicenseAdminEmail(row?.email);
298
317
  } catch {
299
318
  return false;
@@ -352,7 +371,7 @@ function startLicenseSafetyPoll() {
352
371
  const isValid = !!currentLicense?.valid;
353
372
  if (wasValid && !isValid) {
354
373
  console.log("[license] safety poll detected revocation");
355
- handleLicenseRevoked();
374
+ await handleLicenseRevoked();
356
375
  }
357
376
  } catch (err) {
358
377
  console.warn(
@@ -364,10 +383,10 @@ function startLicenseSafetyPoll() {
364
383
  safetyPollHandle.unref();
365
384
  }
366
385
  }
367
- function handleLicenseRevoked() {
386
+ async function handleLicenseRevoked() {
368
387
  try {
369
388
  const db = getDb();
370
- const result = db.prepare("DELETE FROM sessions").run();
389
+ const result = await db.run("DELETE FROM sessions");
371
390
  console.warn(
372
391
  `[license] revoked \u2014 wiped ${result.changes} active session(s).`
373
392
  );
@@ -423,7 +442,10 @@ async function _validateLicenseImpl(forceFresh) {
423
442
  };
424
443
  }
425
444
  const db = getDb();
426
- const cached = db.prepare("SELECT * FROM license_cache WHERE key = ?").get(key);
445
+ const cached = await db.get(
446
+ "SELECT * FROM license_cache WHERE key = ?",
447
+ [key]
448
+ );
427
449
  if (cached && !forceFresh) {
428
450
  const age = Date.now() - (/* @__PURE__ */ new Date(cached.validated_at + "Z")).getTime();
429
451
  if (age < CACHE_TTL_MS) {
@@ -513,7 +535,7 @@ async function _validateLicenseImpl(forceFresh) {
513
535
  suspensionReason = null;
514
536
  }
515
537
  if (!data.valid && !data.suspended) {
516
- db.prepare("DELETE FROM license_cache WHERE key = ?").run(key);
538
+ await db.run("DELETE FROM license_cache WHERE key = ?", [key]);
517
539
  return {
518
540
  valid: false,
519
541
  tier: "none",
@@ -526,15 +548,21 @@ async function _validateLicenseImpl(forceFresh) {
526
548
  }
527
549
  if (data.valid) {
528
550
  const limitsJson = data.limits ? JSON.stringify(data.limits) : null;
529
- db.prepare(
530
- `INSERT OR REPLACE INTO license_cache (key, tier, org, limits_json, owner_email, validated_at)
531
- VALUES (?, ?, ?, ?, ?, datetime('now'))`
532
- ).run(
533
- key,
534
- data.tier || "community",
535
- data.org || null,
536
- limitsJson,
537
- data.owner_email || null
551
+ await db.run(
552
+ insertOrReplace(
553
+ db,
554
+ `INSERT INTO license_cache (key, tier, org, limits_json, owner_email, validated_at)
555
+ VALUES (?, ?, ?, ?, ?, ${nowExpr(db)})`,
556
+ ["key"],
557
+ `tier = excluded.tier, org = excluded.org, limits_json = excluded.limits_json, owner_email = excluded.owner_email, validated_at = ${nowExpr(db)}`
558
+ ),
559
+ [
560
+ key,
561
+ data.tier || "community",
562
+ data.org || null,
563
+ limitsJson,
564
+ data.owner_email || null
565
+ ]
538
566
  );
539
567
  }
540
568
  return {
@@ -574,71 +602,80 @@ async function _validateLicenseImpl(forceFresh) {
574
602
  }
575
603
 
576
604
  // src/nests/service.ts
577
- function resolveNestPath(nestId) {
578
- return join3(config.DATA_ROOT, "nests", nestId);
579
- }
580
- function isImportedNest(nestId) {
581
- const row = getDb().prepare("SELECT is_imported FROM nests WHERE id = ?").get(nestId);
605
+ async function isImportedNest(nestId) {
606
+ const row = await getDb().get(
607
+ "SELECT is_imported FROM nests WHERE id = ?",
608
+ [nestId]
609
+ );
582
610
  return !!row?.is_imported;
583
611
  }
584
612
  function toSlug(name) {
585
613
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
586
614
  }
587
- function uniqueNestName(userId, baseName) {
615
+ async function uniqueNestName(userId, baseName) {
588
616
  const db = getDb();
589
- const slugTaken = db.prepare(
590
- "SELECT 1 FROM nests WHERE user_id = ? AND slug = ? LIMIT 1"
591
- );
592
617
  let candidate = baseName;
593
618
  let n = 0;
594
619
  while (n < 1e3) {
595
620
  const slug = toSlug(candidate);
596
- const hit = slugTaken.get(userId, slug);
621
+ const hit = await db.get(
622
+ "SELECT 1 FROM nests WHERE user_id = ? AND slug = ? LIMIT 1",
623
+ [userId, slug]
624
+ );
597
625
  if (!hit) return candidate;
598
626
  n++;
599
627
  candidate = `${baseName} (${n})`;
600
628
  }
601
629
  return candidate;
602
630
  }
603
- function isStewardshipEnabled(nestId) {
631
+ async function isStewardshipEnabled(nestId) {
604
632
  const db = getDb();
605
- const row = db.prepare("SELECT stewardship_enabled FROM nests WHERE id = ?").get(nestId);
633
+ const row = await db.get(
634
+ "SELECT stewardship_enabled FROM nests WHERE id = ?",
635
+ [nestId]
636
+ );
606
637
  return !!row?.stewardship_enabled;
607
638
  }
608
- function setStewardshipEnabled(nestId, enabled) {
639
+ async function setStewardshipEnabled(nestId, enabled) {
609
640
  const db = getDb();
610
- db.prepare("UPDATE nests SET stewardship_enabled = ? WHERE id = ?").run(
641
+ await db.run("UPDATE nests SET stewardship_enabled = ? WHERE id = ?", [
611
642
  enabled ? 1 : 0,
612
643
  nestId
613
- );
644
+ ]);
614
645
  }
615
- function nestAllowsSelfApprove(nestId) {
646
+ async function nestAllowsSelfApprove(nestId) {
616
647
  const db = getDb();
617
- const row = db.prepare("SELECT allow_self_approve FROM nests WHERE id = ?").get(nestId);
648
+ const row = await db.get(
649
+ "SELECT allow_self_approve FROM nests WHERE id = ?",
650
+ [nestId]
651
+ );
618
652
  return !!row?.allow_self_approve;
619
653
  }
620
- function setAllowSelfApprove(nestId, allow) {
654
+ async function setAllowSelfApprove(nestId, allow) {
621
655
  const db = getDb();
622
- db.prepare("UPDATE nests SET allow_self_approve = ? WHERE id = ?").run(
656
+ await db.run("UPDATE nests SET allow_self_approve = ? WHERE id = ?", [
623
657
  allow ? 1 : 0,
624
658
  nestId
625
- );
659
+ ]);
626
660
  }
627
- function disableStewardshipAndWipeGovernance(nestId) {
661
+ async function disableStewardshipAndWipeGovernance(nestId) {
628
662
  const db = getDb();
629
- const wipe = db.transaction((id) => {
630
- const stewards = db.prepare("DELETE FROM stewards WHERE nest_id = ?").run(id).changes;
631
- const pendingReviews = db.prepare(
632
- "DELETE FROM review_requests WHERE nest_id = ? AND status = 'pending'"
633
- ).run(id).changes;
634
- db.prepare("UPDATE nests SET stewardship_enabled = 0 WHERE id = ?").run(id);
663
+ return db.transaction(async (tx) => {
664
+ const id = nestId;
665
+ const stewards = (await tx.run("DELETE FROM stewards WHERE nest_id = ?", [id])).changes;
666
+ const pendingReviews = (await tx.run(
667
+ "DELETE FROM review_requests WHERE nest_id = ? AND status = 'pending'",
668
+ [id]
669
+ )).changes;
670
+ await tx.run("UPDATE nests SET stewardship_enabled = 0 WHERE id = ?", [id]);
635
671
  return { stewards, pendingReviews };
636
672
  });
637
- return wipe(nestId);
638
673
  }
639
- function renameNest(nestId, patch) {
674
+ async function renameNest(nestId, patch) {
640
675
  const db = getDb();
641
- const existing = db.prepare("SELECT * FROM nests WHERE id = ?").get(nestId);
676
+ const existing = await db.get("SELECT * FROM nests WHERE id = ?", [
677
+ nestId
678
+ ]);
642
679
  if (!existing) {
643
680
  throw new ValidationError("Nest not found");
644
681
  }
@@ -649,9 +686,10 @@ function renameNest(nestId, patch) {
649
686
  if (!trimmed) throw new ValidationError("Name cannot be empty");
650
687
  const slug = toSlug(trimmed);
651
688
  if (!slug) throw new ValidationError("Name must contain at least one alphanumeric character");
652
- const conflict = db.prepare(
653
- "SELECT id FROM nests WHERE user_id = ? AND slug = ? AND id != ? LIMIT 1"
654
- ).get(existing.user_id, slug, nestId);
689
+ const conflict = await db.get(
690
+ "SELECT id FROM nests WHERE user_id = ? AND slug = ? AND id != ? LIMIT 1",
691
+ [existing.user_id, slug, nestId]
692
+ );
655
693
  if (conflict) {
656
694
  throw new ValidationError(
657
695
  `Another nest already uses this name. Pick a different one.`
@@ -668,25 +706,27 @@ function renameNest(nestId, patch) {
668
706
  return existing;
669
707
  }
670
708
  params.push(nestId);
671
- db.prepare(`UPDATE nests SET ${updates.join(", ")} WHERE id = ?`).run(
672
- ...params
709
+ await db.run(
710
+ `UPDATE nests SET ${updates.join(", ")} WHERE id = ?`,
711
+ params
673
712
  );
674
- return db.prepare("SELECT * FROM nests WHERE id = ?").get(nestId);
713
+ return await db.get("SELECT * FROM nests WHERE id = ?", [nestId]);
675
714
  }
676
715
  async function createNest(userId, name, description) {
677
716
  const id = uuid();
678
717
  const slug = toSlug(name);
679
718
  const db = getDb();
680
719
  const visibility = userId === ANON_USER_ID ? "public" : "private";
681
- db.prepare(
682
- "INSERT INTO nests (id, user_id, name, slug, description, visibility) VALUES (?, ?, ?, ?, ?, ?)"
683
- ).run(id, userId, name, slug, description || null, visibility);
720
+ await db.run(
721
+ "INSERT INTO nests (id, user_id, name, slug, description, visibility) VALUES (?, ?, ?, ?, ?, ?)",
722
+ [id, userId, name, slug, description || null, visibility]
723
+ );
684
724
  const path = resolveNestPath(id);
685
725
  mkdirSync2(path, { recursive: true });
686
726
  const storage = new NestStorage(path);
687
727
  await storage.init(name);
688
728
  trackEvent("nest.create", { nestId: id, userId });
689
- return db.prepare("SELECT * FROM nests WHERE id = ?").get(id);
729
+ return await db.get("SELECT * FROM nests WHERE id = ?", [id]);
690
730
  }
691
731
  async function importNest(userId, name, files) {
692
732
  const nm = (name || "").trim();
@@ -709,35 +749,46 @@ async function importNest(userId, name, files) {
709
749
  discardImportDir(dest);
710
750
  throw new ValidationError("Failed to import folder");
711
751
  }
712
- db.prepare(
713
- "INSERT INTO nests (id, user_id, name, slug, description, visibility, is_imported) VALUES (?, ?, ?, ?, ?, ?, 1)"
714
- ).run(id, userId, nm, slug, null, visibility);
752
+ await db.run(
753
+ "INSERT INTO nests (id, user_id, name, slug, description, visibility, is_imported) VALUES (?, ?, ?, ?, ?, ?, 1)",
754
+ [id, userId, nm, slug, null, visibility]
755
+ );
715
756
  trackEvent("nest.import", { nestId: id, userId });
716
- return db.prepare("SELECT * FROM nests WHERE id = ?").get(id);
757
+ return await db.get("SELECT * FROM nests WHERE id = ?", [id]);
717
758
  }
718
- function listNests(userId) {
759
+ async function listNests(userId) {
719
760
  const db = getDb();
720
761
  if (userId === ANON_USER_ID) {
721
- return db.prepare("SELECT * FROM nests WHERE user_id = ? ORDER BY created_at DESC").all(ANON_USER_ID);
762
+ return await db.all(
763
+ "SELECT * FROM nests WHERE user_id = ? ORDER BY created_at DESC",
764
+ [ANON_USER_ID]
765
+ );
722
766
  }
723
- const includeAnon = config.AUTH_MODE === "open" || isLicenseAdminUserId(userId);
767
+ const includeAnon = config.AUTH_MODE === "open" || await isLicenseAdminUserId(userId);
724
768
  if (!includeAnon) {
725
- return db.prepare("SELECT * FROM nests WHERE user_id = ? ORDER BY created_at DESC").all(userId);
769
+ return await db.all(
770
+ "SELECT * FROM nests WHERE user_id = ? ORDER BY created_at DESC",
771
+ [userId]
772
+ );
726
773
  }
727
- return db.prepare(
728
- "SELECT * FROM nests WHERE user_id = ? OR user_id = ? ORDER BY created_at DESC"
729
- ).all(userId, ANON_USER_ID);
774
+ return await db.all(
775
+ "SELECT * FROM nests WHERE user_id = ? OR user_id = ? ORDER BY created_at DESC",
776
+ [userId, ANON_USER_ID]
777
+ );
730
778
  }
731
- function listSharedNests(userId) {
779
+ async function listSharedNests(userId) {
732
780
  const db = getDb();
733
- const caller = db.prepare("SELECT email FROM users WHERE id = ?").get(userId);
734
- const isServerAdmin = isLicenseAdminUserId(userId) || (caller?.email ? isSuperAdmin(caller.email) : false);
781
+ const caller = await db.get("SELECT email FROM users WHERE id = ?", [
782
+ userId
783
+ ]);
784
+ const isServerAdmin = await isLicenseAdminUserId(userId) || (caller?.email ? isSuperAdmin(caller.email) : false);
735
785
  if (isServerAdmin) {
736
- return db.prepare(
737
- "SELECT * FROM nests WHERE user_id != ? ORDER BY created_at DESC"
738
- ).all(userId);
786
+ return await db.all(
787
+ "SELECT * FROM nests WHERE user_id != ? ORDER BY created_at DESC",
788
+ [userId]
789
+ );
739
790
  }
740
- return db.prepare(
791
+ return await db.all(
741
792
  `SELECT DISTINCT n.* FROM nests n
742
793
  LEFT JOIN nest_collaborators nc
743
794
  ON nc.nest_id = n.id AND nc.user_id = ?
@@ -747,12 +798,13 @@ function listSharedNests(userId) {
747
798
  AND LOWER(s.user_email) = LOWER(u.email)
748
799
  WHERE n.user_id != ?
749
800
  AND (nc.user_id IS NOT NULL OR s.id IS NOT NULL)
750
- ORDER BY n.created_at DESC`
751
- ).all(userId, userId, userId);
801
+ ORDER BY n.created_at DESC`,
802
+ [userId, userId, userId]
803
+ );
752
804
  }
753
- function listPublicNests(userId) {
805
+ async function listPublicNests(userId) {
754
806
  const db = getDb();
755
- return db.prepare(
807
+ return await db.all(
756
808
  `SELECT n.* FROM nests n
757
809
  WHERE n.visibility = 'public'
758
810
  AND n.user_id != ?
@@ -760,29 +812,30 @@ function listPublicNests(userId) {
760
812
  SELECT 1 FROM nest_collaborators nc
761
813
  WHERE nc.nest_id = n.id AND nc.user_id = ?
762
814
  )
763
- ORDER BY n.created_at DESC`
764
- ).all(userId, userId);
815
+ ORDER BY n.created_at DESC`,
816
+ [userId, userId]
817
+ );
765
818
  }
766
- function getNest(nestId) {
819
+ async function getNest(nestId) {
767
820
  const db = getDb();
768
- return db.prepare("SELECT * FROM nests WHERE id = ?").get(nestId) || null;
821
+ return await db.get("SELECT * FROM nests WHERE id = ?", [nestId]) || null;
769
822
  }
770
823
  async function deleteNest(nestId) {
771
824
  const db = getDb();
772
- const wipe = db.transaction((id) => {
773
- db.prepare("DELETE FROM approved_versions WHERE nest_id = ?").run(id);
774
- db.prepare("DELETE FROM node_versions WHERE nest_id = ?").run(id);
775
- db.prepare("DELETE FROM review_requests WHERE nest_id = ?").run(id);
776
- db.prepare("DELETE FROM stewards WHERE nest_id = ?").run(id);
777
- db.prepare("DELETE FROM nest_collaborators WHERE nest_id = ?").run(id);
825
+ await db.transaction(async (tx) => {
826
+ const id = nestId;
827
+ await tx.run("DELETE FROM approved_versions WHERE nest_id = ?", [id]);
828
+ await tx.run("DELETE FROM node_versions WHERE nest_id = ?", [id]);
829
+ await tx.run("DELETE FROM review_requests WHERE nest_id = ?", [id]);
830
+ await tx.run("DELETE FROM stewards WHERE nest_id = ?", [id]);
831
+ await tx.run("DELETE FROM nest_collaborators WHERE nest_id = ?", [id]);
778
832
  try {
779
- db.prepare("DELETE FROM node_tag_index WHERE nest_id = ?").run(id);
833
+ await tx.run("DELETE FROM node_tag_index WHERE nest_id = ?", [id]);
780
834
  } catch {
781
835
  }
782
- db.prepare("DELETE FROM api_keys WHERE nest_id = ?").run(id);
783
- db.prepare("DELETE FROM nests WHERE id = ?").run(id);
836
+ await tx.run("DELETE FROM api_keys WHERE nest_id = ?", [id]);
837
+ await tx.run("DELETE FROM nests WHERE id = ?", [id]);
784
838
  });
785
- wipe(nestId);
786
839
  const path = resolveNestPath(nestId);
787
840
  try {
788
841
  rmSync2(path, { recursive: true, force: true });
@@ -840,11 +893,11 @@ var FlatNestStorage = class extends NestStorage2 {
840
893
  // src/nodes/engine.ts
841
894
  var NestEngineCache = class {
842
895
  cache = /* @__PURE__ */ new Map();
843
- get(nestId) {
896
+ async get(nestId) {
844
897
  let engine = this.cache.get(nestId);
845
898
  if (!engine) {
846
899
  const nestPath = resolveNestPath(nestId);
847
- const storage = isImportedNest(nestId) ? new FlatNestStorage(nestPath) : new NestStorage3(nestPath);
900
+ const storage = await isImportedNest(nestId) ? new FlatNestStorage(nestPath) : new NestStorage3(nestPath);
848
901
  const query = new GraphQueryEngine(storage);
849
902
  const versions = new VersionManager(storage);
850
903
  engine = { storage, query, versions };
@@ -861,7 +914,7 @@ var engineCache = new NestEngineCache();
861
914
  // src/governance/title-resolver.ts
862
915
  async function buildTitleMap(nestId) {
863
916
  try {
864
- const { storage } = engineCache.get(nestId);
917
+ const { storage } = await engineCache.get(nestId);
865
918
  const docs = await storage.discoverDocuments();
866
919
  return new Map(docs.map((d) => [d.id, d.frontmatter.title]));
867
920
  } catch {
@@ -877,30 +930,32 @@ var PERMISSION_LEVELS = {
877
930
  admin: 3,
878
931
  owner: 4
879
932
  };
880
- function resolveNestPermission(nestId, userId) {
933
+ async function resolveNestPermission(nestId, userId) {
881
934
  const db = getDb();
882
- const nest = db.prepare("SELECT user_id, visibility FROM nests WHERE id = ?").get(nestId);
935
+ const nest = await db.get("SELECT user_id, visibility FROM nests WHERE id = ?", [nestId]);
883
936
  if (!nest) return "none";
884
937
  if (nest.user_id === userId) return "owner";
885
- if (nest.user_id === ANON_USER_ID && isLicenseAdminUserId(userId)) {
938
+ if (nest.user_id === ANON_USER_ID && await isLicenseAdminUserId(userId)) {
886
939
  return "owner";
887
940
  }
888
- if (isLicenseAdminUserId(userId)) return "admin";
889
- const caller = db.prepare("SELECT email FROM users WHERE id = ?").get(userId);
941
+ if (await isLicenseAdminUserId(userId)) return "admin";
942
+ const caller = await db.get("SELECT email FROM users WHERE id = ?", [userId]);
890
943
  if (caller?.email && isSuperAdmin(caller.email)) {
891
944
  return "admin";
892
945
  }
893
- const directGrant = db.prepare(
894
- "SELECT permission FROM nest_collaborators WHERE nest_id = ? AND user_id = ?"
895
- ).get(nestId, userId);
946
+ const directGrant = await db.get(
947
+ "SELECT permission FROM nest_collaborators WHERE nest_id = ? AND user_id = ?",
948
+ [nestId, userId]
949
+ );
896
950
  if (directGrant) return directGrant.permission;
897
- const stewardGrant = db.prepare(
951
+ const stewardGrant = await db.get(
898
952
  `SELECT 1 FROM stewards s
899
953
  JOIN users u ON u.id = ?
900
954
  WHERE s.nest_id = ? AND s.is_active = 1
901
955
  AND LOWER(s.user_email) = LOWER(u.email)
902
- LIMIT 1`
903
- ).get(userId, nestId);
956
+ LIMIT 1`,
957
+ [userId, nestId]
958
+ );
904
959
  if (stewardGrant) return "read";
905
960
  if (nest.visibility === "public") return "read";
906
961
  return "none";
@@ -908,26 +963,28 @@ function resolveNestPermission(nestId, userId) {
908
963
  function permissionLevel(p) {
909
964
  return PERMISSION_LEVELS[p] ?? 0;
910
965
  }
911
- function isPublicReader(nestId, userId) {
966
+ async function isPublicReader(nestId, userId) {
912
967
  const db = getDb();
913
- const nest = db.prepare("SELECT user_id, visibility FROM nests WHERE id = ?").get(nestId);
968
+ const nest = await db.get("SELECT user_id, visibility FROM nests WHERE id = ?", [nestId]);
914
969
  if (!nest) return false;
915
970
  if (nest.visibility !== "public") return false;
916
971
  if (nest.user_id === userId) return false;
917
- const directGrant = db.prepare(
918
- "SELECT 1 FROM nest_collaborators WHERE nest_id = ? AND user_id = ?"
919
- ).get(nestId, userId);
972
+ const directGrant = await db.get(
973
+ "SELECT 1 FROM nest_collaborators WHERE nest_id = ? AND user_id = ?",
974
+ [nestId, userId]
975
+ );
920
976
  if (directGrant) return false;
921
- if (isLicenseAdminUserId(userId)) return false;
922
- const caller = db.prepare("SELECT email FROM users WHERE id = ?").get(userId);
977
+ if (await isLicenseAdminUserId(userId)) return false;
978
+ const caller = await db.get("SELECT email FROM users WHERE id = ?", [userId]);
923
979
  if (caller?.email && isSuperAdmin(caller.email)) return false;
924
- const stewardGrant = db.prepare(
980
+ const stewardGrant = await db.get(
925
981
  `SELECT 1 FROM stewards s
926
982
  JOIN users u ON u.id = ?
927
983
  WHERE s.nest_id = ? AND s.is_active = 1
928
984
  AND LOWER(s.user_email) = LOWER(u.email)
929
- LIMIT 1`
930
- ).get(userId, nestId);
985
+ LIMIT 1`,
986
+ [userId, nestId]
987
+ );
931
988
  if (stewardGrant) return false;
932
989
  return true;
933
990
  }
@@ -964,35 +1021,39 @@ function primaryRole(roles) {
964
1021
  }
965
1022
 
966
1023
  // src/governance/stewardship-service.ts
967
- function assignSteward(data) {
1024
+ async function assignSteward(data) {
968
1025
  const db = getDb();
969
1026
  const id = uuid2();
970
- db.prepare(
1027
+ await db.run(
971
1028
  `INSERT INTO stewards
972
1029
  (id, nest_id, scope, node_pattern, tag_name, user_email, user_id, role, assigned_by, is_active)
973
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
974
- ).run(
975
- id,
976
- data.nestId,
977
- data.scope,
978
- data.nodePattern || null,
979
- data.tagName || null,
980
- data.userEmail,
981
- data.userId || null,
982
- data.role,
983
- data.assignedBy,
984
- data.isActive ? 1 : 0
1030
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1031
+ [
1032
+ id,
1033
+ data.nestId,
1034
+ data.scope,
1035
+ data.nodePattern || null,
1036
+ data.tagName || null,
1037
+ data.userEmail,
1038
+ data.userId || null,
1039
+ data.role,
1040
+ data.assignedBy,
1041
+ data.isActive ? 1 : 0
1042
+ ]
985
1043
  );
986
1044
  return { ...data, id };
987
1045
  }
988
- function removeSteward(id) {
1046
+ async function removeSteward(id) {
989
1047
  const db = getDb();
990
- db.prepare("DELETE FROM stewards WHERE id = ?").run(id);
1048
+ await db.run("DELETE FROM stewards WHERE id = ?", [id]);
991
1049
  }
992
1050
  var VALID_STEWARD_ROLES = ["editor", "reviewer", "viewer"];
993
- function updateSteward(id, update) {
1051
+ async function updateSteward(id, update) {
994
1052
  const db = getDb();
995
- const current = db.prepare("SELECT * FROM stewards WHERE id = ? AND is_active = 1").get(id);
1053
+ const current = await db.get(
1054
+ "SELECT * FROM stewards WHERE id = ? AND is_active = 1",
1055
+ [id]
1056
+ );
996
1057
  if (!current) {
997
1058
  throw new ValidationError("Steward not found");
998
1059
  }
@@ -1023,13 +1084,14 @@ function updateSteward(id, update) {
1023
1084
  case "nest":
1024
1085
  break;
1025
1086
  }
1026
- const dup = db.prepare(
1087
+ const dup = await db.get(
1027
1088
  `SELECT role FROM stewards
1028
1089
  WHERE nest_id = ? AND is_active = 1 AND id != ? AND scope = ?
1029
1090
  AND user_email = ?
1030
1091
  AND COALESCE(node_pattern, '') = COALESCE(?, '')
1031
- AND COALESCE(tag_name, '') = COALESCE(?, '')`
1032
- ).get(current.nest_id, id, scope, current.user_email, nodePattern, tagName);
1092
+ AND COALESCE(tag_name, '') = COALESCE(?, '')`,
1093
+ [current.nest_id, id, scope, current.user_email, nodePattern, tagName]
1094
+ );
1033
1095
  if (dup) {
1034
1096
  const scopeLabel = scope === "document" ? `document "${nodePattern}"` : scope === "tag" ? `tag "#${tagName}"` : "this nest";
1035
1097
  throw new ConflictError(
@@ -1037,26 +1099,30 @@ function updateSteward(id, update) {
1037
1099
  );
1038
1100
  }
1039
1101
  }
1040
- db.prepare(
1041
- "UPDATE stewards SET role = ?, scope = ?, node_pattern = ?, tag_name = ? WHERE id = ?"
1042
- ).run(role, scope, nodePattern, tagName, id);
1043
- const row = db.prepare("SELECT * FROM stewards WHERE id = ?").get(id);
1102
+ await db.run(
1103
+ "UPDATE stewards SET role = ?, scope = ?, node_pattern = ?, tag_name = ? WHERE id = ?",
1104
+ [role, scope, nodePattern, tagName, id]
1105
+ );
1106
+ const row = await db.get("SELECT * FROM stewards WHERE id = ?", [id]);
1044
1107
  return rowToSteward(row);
1045
1108
  }
1046
- function updateStewardRole(id, role) {
1109
+ async function updateStewardRole(id, role) {
1047
1110
  return updateSteward(id, { role });
1048
1111
  }
1049
- function getSteward(id) {
1112
+ async function getSteward(id) {
1050
1113
  const db = getDb();
1051
- const row = db.prepare("SELECT * FROM stewards WHERE id = ?").get(id);
1114
+ const row = await db.get("SELECT * FROM stewards WHERE id = ?", [id]);
1052
1115
  return row ? rowToSteward(row) : null;
1053
1116
  }
1054
1117
  async function getStewardsForNest(nestId) {
1055
1118
  const db = getDb();
1056
- const rows = db.prepare("SELECT * FROM stewards WHERE nest_id = ? AND is_active = 1").all(nestId);
1119
+ const rows = await db.all(
1120
+ "SELECT * FROM stewards WHERE nest_id = ? AND is_active = 1",
1121
+ [nestId]
1122
+ );
1057
1123
  return enrichWithTitles(nestId, rows.map(rowToSteward));
1058
1124
  }
1059
- function getStewardsForScope(params) {
1125
+ async function getStewardsForScope(params) {
1060
1126
  const db = getDb();
1061
1127
  let sql = "SELECT * FROM stewards WHERE nest_id = ? AND is_active = 1";
1062
1128
  const args = [params.nestId];
@@ -1068,7 +1134,7 @@ function getStewardsForScope(params) {
1068
1134
  sql += " AND (node_pattern = ? OR tag_name = ?)";
1069
1135
  args.push(params.scopeTarget, params.scopeTarget);
1070
1136
  }
1071
- return db.prepare(sql).all(...args).map(rowToSteward);
1137
+ return (await db.all(sql, args)).map(rowToSteward);
1072
1138
  }
1073
1139
  async function listStewards(params) {
1074
1140
  const db = getDb();
@@ -1084,7 +1150,7 @@ async function listStewards(params) {
1084
1150
  args.push(like, like, like);
1085
1151
  }
1086
1152
  sql += " ORDER BY scope, COALESCE(node_pattern, tag_name, ''), user_email";
1087
- const stewards = db.prepare(sql).all(...args).map(rowToSteward);
1153
+ const stewards = (await db.all(sql, args)).map(rowToSteward);
1088
1154
  return enrichWithTitles(params.nestId, stewards);
1089
1155
  }
1090
1156
  async function enrichWithTitles(nestId, stewards) {
@@ -1118,7 +1184,7 @@ async function createStewardRecord(params) {
1118
1184
  const db = getDb();
1119
1185
  const results = [];
1120
1186
  const actor = (params.assignedBy || "").trim().toLowerCase();
1121
- const ownerEmail = (getNestOwnerEmail(params.nestId) || "").toLowerCase();
1187
+ const ownerEmail = (await getNestOwnerEmail(params.nestId) || "").toLowerCase();
1122
1188
  for (const user of params.users) {
1123
1189
  const email = user.email.trim().toLowerCase();
1124
1190
  if (!email) continue;
@@ -1132,17 +1198,18 @@ async function createStewardRecord(params) {
1132
1198
  "The nest owner already has full access and doesn't need a steward role."
1133
1199
  );
1134
1200
  }
1135
- const existing = db.prepare(
1201
+ const existing = await db.get(
1136
1202
  `SELECT * FROM stewards
1137
1203
  WHERE nest_id = ? AND is_active = 1 AND scope = ? AND user_email = ?
1138
1204
  AND COALESCE(node_pattern, '') = COALESCE(?, '')
1139
- AND COALESCE(tag_name, '') = COALESCE(?, '')`
1140
- ).get(
1141
- params.nestId,
1142
- params.scope,
1143
- email,
1144
- nodePattern ?? null,
1145
- tagName ?? null
1205
+ AND COALESCE(tag_name, '') = COALESCE(?, '')`,
1206
+ [
1207
+ params.nestId,
1208
+ params.scope,
1209
+ email,
1210
+ nodePattern ?? null,
1211
+ tagName ?? null
1212
+ ]
1146
1213
  );
1147
1214
  if (existing) {
1148
1215
  const scopeLabel = params.scope === "document" ? `document "${nodePattern}"` : params.scope === "tag" ? `tag "#${tagName}"` : "this nest";
@@ -1150,8 +1217,11 @@ async function createStewardRecord(params) {
1150
1217
  `"${email}" is already a steward of ${scopeLabel} with the "${existing.role}" role. Remove the existing assignment first to change the role.`
1151
1218
  );
1152
1219
  }
1153
- const userRow = db.prepare("SELECT id FROM users WHERE email = ?").get(email);
1154
- const created = assignSteward({
1220
+ const userRow = await db.get(
1221
+ "SELECT id FROM users WHERE email = ?",
1222
+ [email]
1223
+ );
1224
+ const created = await assignSteward({
1155
1225
  nestId: params.nestId,
1156
1226
  scope: params.scope,
1157
1227
  nodePattern,
@@ -1165,20 +1235,21 @@ async function createStewardRecord(params) {
1165
1235
  });
1166
1236
  results.push(created);
1167
1237
  }
1168
- db.prepare(
1169
- "UPDATE nests SET stewardship_enabled = 1 WHERE id = ? AND stewardship_enabled = 0"
1170
- ).run(params.nestId);
1238
+ await db.run(
1239
+ "UPDATE nests SET stewardship_enabled = 1 WHERE id = ? AND stewardship_enabled = 0",
1240
+ [params.nestId]
1241
+ );
1171
1242
  return results;
1172
1243
  }
1173
- function resolveStewardsForNode(nestId, nodeId) {
1174
- return resolve(nestId, nodeId).stewards;
1244
+ async function resolveStewardsForNode(nestId, nodeId) {
1245
+ return (await resolve(nestId, nodeId)).stewards;
1175
1246
  }
1176
- function resolveStewardsWithFallback(nestId, nodeId) {
1247
+ async function resolveStewardsWithFallback(nestId, nodeId) {
1177
1248
  return resolve(nestId, nodeId);
1178
1249
  }
1179
- function resolve(nestId, nodeId) {
1250
+ async function resolve(nestId, nodeId) {
1180
1251
  const db = getDb();
1181
- const rows = db.prepare(
1252
+ const rows = await db.all(
1182
1253
  `
1183
1254
  SELECT s.*, 1 AS priority, ('document: ' || s.node_pattern) AS match_source
1184
1255
  FROM stewards s
@@ -1197,16 +1268,17 @@ function resolve(nestId, nodeId) {
1197
1268
  FROM stewards s
1198
1269
  WHERE s.nest_id = ? AND s.is_active = 1 AND s.scope = 'nest'
1199
1270
  ORDER BY priority ASC, user_email ASC
1200
- `
1201
- ).all(
1202
- nestId,
1203
- nodeId,
1204
- // document branch
1205
- nestId,
1206
- nodeId,
1207
- // tag branch
1208
- nestId
1209
- // nest branch
1271
+ `,
1272
+ [
1273
+ nestId,
1274
+ nodeId,
1275
+ // document branch
1276
+ nestId,
1277
+ nodeId,
1278
+ // tag branch
1279
+ nestId
1280
+ // nest branch
1281
+ ]
1210
1282
  );
1211
1283
  const resolved = rows.map((row) => ({
1212
1284
  steward: rowToSteward(row),
@@ -1216,60 +1288,67 @@ function resolve(nestId, nodeId) {
1216
1288
  if (resolved.length > 0) {
1217
1289
  return { stewards: resolved, fallbackToOwner: false };
1218
1290
  }
1219
- const owner = db.prepare(
1291
+ const owner = await db.get(
1220
1292
  `SELECT u.email FROM nests n
1221
1293
  JOIN users u ON u.id = n.user_id
1222
- WHERE n.id = ?`
1223
- ).get(nestId);
1294
+ WHERE n.id = ?`,
1295
+ [nestId]
1296
+ );
1224
1297
  return {
1225
1298
  stewards: [],
1226
1299
  fallbackToOwner: true,
1227
1300
  ownerEmail: owner?.email
1228
1301
  };
1229
1302
  }
1230
- function userIdForEmail(email) {
1303
+ async function userIdForEmail(email) {
1231
1304
  const db = getDb();
1232
- const row = db.prepare("SELECT id FROM users WHERE LOWER(email) = LOWER(?)").get(email);
1305
+ const row = await db.get(
1306
+ "SELECT id FROM users WHERE LOWER(email) = LOWER(?)",
1307
+ [email]
1308
+ );
1233
1309
  return row?.id;
1234
1310
  }
1235
- function getStewardRolesForUser(nestId, userEmail) {
1311
+ async function getStewardRolesForUser(nestId, userEmail) {
1236
1312
  const db = getDb();
1237
- const rows = db.prepare(
1238
- "SELECT DISTINCT role FROM stewards WHERE nest_id = ? AND is_active = 1 AND LOWER(user_email) = LOWER(?)"
1239
- ).all(nestId, userEmail);
1313
+ const rows = await db.all(
1314
+ "SELECT DISTINCT role FROM stewards WHERE nest_id = ? AND is_active = 1 AND LOWER(user_email) = LOWER(?)",
1315
+ [nestId, userEmail]
1316
+ );
1240
1317
  return rows.map((r) => r.role);
1241
1318
  }
1242
- function getStewardsForUser(nestId, userEmail) {
1319
+ async function getStewardsForUser(nestId, userEmail) {
1243
1320
  const db = getDb();
1244
- const rows = db.prepare(
1245
- "SELECT * FROM stewards WHERE nest_id = ? AND is_active = 1 AND LOWER(user_email) = LOWER(?)"
1246
- ).all(nestId, userEmail);
1321
+ const rows = await db.all(
1322
+ "SELECT * FROM stewards WHERE nest_id = ? AND is_active = 1 AND LOWER(user_email) = LOWER(?)",
1323
+ [nestId, userEmail]
1324
+ );
1247
1325
  return rows.map(rowToSteward);
1248
1326
  }
1249
- function getCollaboratorRole(nestId, userEmail) {
1250
- const userId = userIdForEmail(userEmail);
1327
+ async function getCollaboratorRole(nestId, userEmail) {
1328
+ const userId = await userIdForEmail(userEmail);
1251
1329
  if (!userId) return null;
1252
1330
  const db = getDb();
1253
- const row = db.prepare(
1254
- "SELECT permission FROM nest_collaborators WHERE nest_id = ? AND user_id = ?"
1255
- ).get(nestId, userId);
1331
+ const row = await db.get(
1332
+ "SELECT permission FROM nest_collaborators WHERE nest_id = ? AND user_id = ?",
1333
+ [nestId, userId]
1334
+ );
1256
1335
  return row?.permission ?? null;
1257
1336
  }
1258
- function resolveUserRoles(nestId, userEmail, opts) {
1337
+ async function resolveUserRoles(nestId, userEmail, opts) {
1259
1338
  const roles = /* @__PURE__ */ new Set();
1260
1339
  if (isSuperAdmin(userEmail)) roles.add("admin");
1261
- const owner = getNestOwnerEmail(nestId);
1340
+ const owner = await getNestOwnerEmail(nestId);
1262
1341
  if (owner && owner.toLowerCase() === userEmail.toLowerCase()) {
1263
1342
  roles.add("owner");
1264
1343
  }
1265
- const userId = userIdForEmail(userEmail);
1344
+ const userId = await userIdForEmail(userEmail);
1266
1345
  if (userId) {
1267
- const collabRole = collabPermToRole(resolveNestPermission(nestId, userId));
1346
+ const collabRole = collabPermToRole(await resolveNestPermission(nestId, userId));
1268
1347
  if (collabRole) roles.add(collabRole);
1269
1348
  }
1270
- const stewardRoles = opts?.nodeId ? resolveStewardsForNode(nestId, opts.nodeId).filter(
1349
+ const stewardRoles = opts?.nodeId ? (await resolveStewardsForNode(nestId, opts.nodeId)).filter(
1271
1350
  (r) => r.steward.userEmail.toLowerCase() === userEmail.toLowerCase()
1272
- ).map((r) => r.steward.role) : getStewardRolesForUser(nestId, userEmail);
1351
+ ).map((r) => r.steward.role) : await getStewardRolesForUser(nestId, userEmail);
1273
1352
  for (const role of stewardRoles) roles.add(role);
1274
1353
  return [...roles];
1275
1354
  }
@@ -1277,28 +1356,29 @@ function canManageStewards(userEmail) {
1277
1356
  if (config.AUTH_MODE === "open") return true;
1278
1357
  return isLicenseAdminEmail(userEmail) || isSuperAdmin(userEmail);
1279
1358
  }
1280
- function canCreateInNest(nestId, userEmail) {
1359
+ async function canCreateInNest(nestId, userEmail) {
1281
1360
  if (config.AUTH_MODE === "open" || isSuperAdmin(userEmail)) return true;
1282
- const userId = userIdForEmail(userEmail);
1361
+ const userId = await userIdForEmail(userEmail);
1283
1362
  if (userId) {
1284
- const perm = resolveNestPermission(nestId, userId);
1363
+ const perm = await resolveNestPermission(nestId, userId);
1285
1364
  if (perm === "owner" || perm === "admin" || perm === "write") return true;
1286
1365
  }
1287
- return getStewardsForUser(nestId, userEmail).some(
1366
+ return (await getStewardsForUser(nestId, userEmail)).some(
1288
1367
  (s) => s.role === "editor" && s.scope === "nest"
1289
1368
  );
1290
1369
  }
1291
- function getNestOwnerEmail(nestId) {
1370
+ async function getNestOwnerEmail(nestId) {
1292
1371
  const db = getDb();
1293
- const row = db.prepare(
1372
+ const row = await db.get(
1294
1373
  `SELECT u.email FROM nests n
1295
1374
  JOIN users u ON u.id = n.user_id
1296
- WHERE n.id = ?`
1297
- ).get(nestId);
1375
+ WHERE n.id = ?`,
1376
+ [nestId]
1377
+ );
1298
1378
  return row?.email ?? null;
1299
1379
  }
1300
- function canUserEdit(nestId, nodeId, userEmail) {
1301
- const roles = resolveUserRoles(nestId, userEmail, { nodeId });
1380
+ async function canUserEdit(nestId, nodeId, userEmail) {
1381
+ const roles = await resolveUserRoles(nestId, userEmail, { nodeId });
1302
1382
  if (roles.includes("owner")) {
1303
1383
  return { allowed: true, reason: "nest owner", role: "owner" };
1304
1384
  }
@@ -1314,31 +1394,33 @@ function canUserEdit(nestId, nodeId, userEmail) {
1314
1394
  }
1315
1395
  return { allowed: false, reason: "no editor role on this node", role: null };
1316
1396
  }
1317
- function getCurrentVersionAuthor(nestId, nodeId) {
1397
+ async function getCurrentVersionAuthor(nestId, nodeId) {
1318
1398
  const db = getDb();
1319
- const row = db.prepare(
1399
+ const row = await db.get(
1320
1400
  `SELECT author FROM node_versions
1321
1401
  WHERE nest_id = ? AND node_id = ?
1322
- ORDER BY version DESC LIMIT 1`
1323
- ).get(nestId, nodeId);
1402
+ ORDER BY version DESC LIMIT 1`,
1403
+ [nestId, nodeId]
1404
+ );
1324
1405
  return row?.author ?? null;
1325
1406
  }
1326
- function getPendingReviewRequester(nestId, nodeId) {
1407
+ async function getPendingReviewRequester(nestId, nodeId) {
1327
1408
  const db = getDb();
1328
- const row = db.prepare(
1409
+ const row = await db.get(
1329
1410
  `SELECT requested_by FROM review_requests
1330
1411
  WHERE nest_id = ? AND node_id = ? AND status = 'pending'
1331
- ORDER BY requested_at DESC LIMIT 1`
1332
- ).get(nestId, nodeId);
1412
+ ORDER BY requested_at DESC LIMIT 1`,
1413
+ [nestId, nodeId]
1414
+ );
1333
1415
  return row?.requested_by ?? null;
1334
1416
  }
1335
- function canUserApprove(nestId, nodeId, userEmail) {
1336
- const roles = resolveUserRoles(nestId, userEmail, { nodeId });
1417
+ async function canUserApprove(nestId, nodeId, userEmail) {
1418
+ const roles = await resolveUserRoles(nestId, userEmail, { nodeId });
1337
1419
  const isOwner = roles.includes("owner");
1338
1420
  const isSuper = isSuperAdmin(userEmail);
1339
- const allowSelf = nestAllowsSelfApprove(nestId);
1421
+ const allowSelf = await nestAllowsSelfApprove(nestId);
1340
1422
  const hasStewardApprove = roles.includes("admin") || roles.includes("reviewer");
1341
- const actor = getPendingReviewRequester(nestId, nodeId) ?? getCurrentVersionAuthor(nestId, nodeId);
1423
+ const actor = await getPendingReviewRequester(nestId, nodeId) ?? await getCurrentVersionAuthor(nestId, nodeId);
1342
1424
  const isOwnSubmission = !!actor && actor.toLowerCase() === userEmail.toLowerCase();
1343
1425
  if (hasStewardApprove) {
1344
1426
  if (isOwnSubmission && !((isOwner || isSuper) && allowSelf)) {
@@ -1375,8 +1457,8 @@ function canUserApprove(nestId, nodeId, userEmail) {
1375
1457
  role: held
1376
1458
  };
1377
1459
  }
1378
- function canUserAccess(nestId, nodeId, userEmail) {
1379
- const roles = resolveUserRoles(nestId, userEmail, { nodeId });
1460
+ async function canUserAccess(nestId, nodeId, userEmail) {
1461
+ const roles = await resolveUserRoles(nestId, userEmail, { nodeId });
1380
1462
  if (roles.includes("owner")) {
1381
1463
  return { allowed: true, reason: "nest owner", role: "owner" };
1382
1464
  }
@@ -1392,21 +1474,26 @@ function canUserAccess(nestId, nodeId, userEmail) {
1392
1474
  }
1393
1475
  return { allowed: false, reason: "no collaborator or steward access", role: null };
1394
1476
  }
1395
- function syncFromConfig(nestId, config2) {
1477
+ async function syncFromConfig(nestId, config2) {
1396
1478
  const db = getDb();
1397
1479
  let count = 0;
1398
- db.prepare(
1399
- "UPDATE stewards SET is_active = 0 WHERE nest_id = ?"
1400
- ).run(nestId);
1401
- db.prepare(
1402
- "UPDATE nests SET stewardship_enabled = 1 WHERE id = ?"
1403
- ).run(nestId);
1404
- const addEntries = (scope, entries, target) => {
1480
+ await db.run(
1481
+ "UPDATE stewards SET is_active = 0 WHERE nest_id = ?",
1482
+ [nestId]
1483
+ );
1484
+ await db.run(
1485
+ "UPDATE nests SET stewardship_enabled = 1 WHERE id = ?",
1486
+ [nestId]
1487
+ );
1488
+ const addEntries = async (scope, entries, target) => {
1405
1489
  for (const entry of entries) {
1406
- const user = db.prepare("SELECT id FROM users WHERE email = ?").get(entry.email);
1490
+ const user = await db.get(
1491
+ "SELECT id FROM users WHERE email = ?",
1492
+ [entry.email]
1493
+ );
1407
1494
  const rawRole = entry.role || "reviewer";
1408
1495
  const role = rawRole === "admin" ? "reviewer" : rawRole;
1409
- assignSteward({
1496
+ await assignSteward({
1410
1497
  nestId,
1411
1498
  scope,
1412
1499
  nodePattern: target?.nodePattern,
@@ -1422,16 +1509,16 @@ function syncFromConfig(nestId, config2) {
1422
1509
  }
1423
1510
  };
1424
1511
  if (config2.nest) {
1425
- addEntries("nest", config2.nest);
1512
+ await addEntries("nest", config2.nest);
1426
1513
  }
1427
1514
  if (config2.tags) {
1428
1515
  for (const [tagName, entries] of Object.entries(config2.tags)) {
1429
- addEntries("tag", entries, { tagName });
1516
+ await addEntries("tag", entries, { tagName });
1430
1517
  }
1431
1518
  }
1432
1519
  if (config2.documents) {
1433
1520
  for (const [docPattern, entries] of Object.entries(config2.documents)) {
1434
- addEntries("document", entries, { nodePattern: docPattern });
1521
+ await addEntries("document", entries, { nodePattern: docPattern });
1435
1522
  }
1436
1523
  }
1437
1524
  return count;
@@ -1464,6 +1551,7 @@ export {
1464
1551
  getCurrentLicense,
1465
1552
  isLicenseAdminEmail,
1466
1553
  isLicenseAdminUserId,
1554
+ upsertEnvVar,
1467
1555
  installLicenseKey,
1468
1556
  startLicenseSafetyPoll,
1469
1557
  isSuspended,
@@ -1474,6 +1562,8 @@ export {
1474
1562
  resolveNestPermission,
1475
1563
  permissionLevel,
1476
1564
  isPublicReader,
1565
+ nestStorageRoot,
1566
+ resolveNestPath,
1477
1567
  uniqueNestName,
1478
1568
  isStewardshipEnabled,
1479
1569
  setStewardshipEnabled,