@happyvertical/smrt-cli 0.55.1 → 0.55.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/AGENTS.md +1 -1
  2. package/agents/db-migrate-concurrency.md +55 -0
  3. package/dist/{AuditLogCollection-WDHL-_AF-G2UOSM3H.js → AuditLogCollection-WDHL-_AF-Bgyxf5sF.js} +1 -1
  4. package/dist/{OidcIdentity-BjRu3n1g-CzvSN6Wz.js → OidcIdentity-BUblgQLs-B4mzosmT.js} +3 -3
  5. package/dist/{OidcIdentityCollection-B4jYh36_-6UWtXCwi.js → OidcIdentityCollection-ClOyaHeF-DqB9Ex4b.js} +4 -4
  6. package/dist/{ProfileAssetCollection-HeMtjBc0-Cz4MOeSB.js → ProfileAssetCollection-CRMcjLJ--CIoZ9Fd2.js} +6 -6
  7. package/dist/{ProfileCollection-Bt6IJK49-CFyOiVFz.js → ProfileCollection-C4INKh7Y-DU6dPYSG.js} +24 -19
  8. package/dist/{ProfileLinkCollection-u1hhzKTm-CAfIjZ4f.js → ProfileLinkCollection-DFfZRsgv-qtgfWKFC.js} +5 -5
  9. package/dist/{ProfileMetadataCollection-BIYKy4Y3-CqcaAHAJ.js → ProfileMetadataCollection-BIYKy4Y3-BS9BY4vO.js} +1 -1
  10. package/dist/{ProfileMetafieldCollection-Buz7gzag-BNRo6e4x.js → ProfileMetafieldCollection-Buz7gzag-DyUWqbh9.js} +1 -1
  11. package/dist/{ProfileRelationshipCollection-C4g_QLI4-DeJzJ_hF.js → ProfileRelationshipCollection-C4g_QLI4-Ci7IHfeW.js} +1 -1
  12. package/dist/ProfileTypeCollection-B7kwi1mU-6KYMumQp.js +103 -0
  13. package/dist/commands/utilities.d.ts.map +1 -1
  14. package/dist/{commands-CboMy2vr.js → commands-KDgVSMM3.js} +46 -27
  15. package/dist/{config-hqNo4lzH.js → config-CdJRSMRi.js} +2 -1
  16. package/dist/config.d.ts.map +1 -1
  17. package/dist/{dist-F98s-wG1.js → dist-C8iSkjym.js} +4 -4
  18. package/dist/{dist-m9bKxs6w.js → dist-CTMZBIem.js} +16 -15
  19. package/dist/dist-CoCIu5cW.js +21 -0
  20. package/dist/{dist-N7UoPf_4.js → dist-DQg_1csT.js} +3 -3
  21. package/dist/{dist-wrk3GKo3.js → dist-DRSQUACn.js} +26 -6
  22. package/dist/index.js +14 -14
  23. package/dist/{json-validator-BiAdsE1O.js → json-validator-CF2oQNDw.js} +1 -1
  24. package/dist/oidcProvisioningPrimitives-DNP9EZlU-CUDQKgU2.js +93 -0
  25. package/dist/{resolveIdentity-CvGDegU6-BePPF1Iv.js → resolveIdentity-CkyqvctR-DFVUhcM1.js} +17 -17
  26. package/package.json +16 -16
  27. package/dist/dist-CkONQFUR.js +0 -20
  28. package/dist/oidcProvisioningPrimitives-CYood-2M-DKUWB1oI.js +0 -171
package/AGENTS.md CHANGED
@@ -11,7 +11,7 @@ smrt doctor --db # Add the live-schema parity section (see below)
11
11
  smrt db:status # Pending schema changes + failed migration classification (+ tenant natural-key findings: a missing tenant-led unique exits 1, a surviving legacy global unique warns until `db:migrate --drop-legacy-natural-key`: src/commands/tenant-natural-keys.ts)
12
12
  smrt db:status --parity # Same, plus live-schema parity (see below)
13
13
  smrt db:orphans # agents/db-orphans.md
14
- smrt db:migrate # agents/type-drift.md
14
+ smrt db:migrate # agents/type-drift.md, agents/db-migrate-concurrency.md
15
15
  smrt db:migrate --postgres-safe # PostgreSQL concurrent-index mode (see below)
16
16
  smrt db:migrate --force-migration <exact-id> [--force-migration <exact-id>...] # Force exact generated migrations in one atomic batch
17
17
  smrt db:migrate --apply-unblocked / --null-orphans # agents/db-migrate-partial-apply.md
@@ -0,0 +1,55 @@
1
+ # `db:migrate` concurrency (#3634)
2
+
3
+ Two processes migrating one PostgreSQL database at once — two workloads that
4
+ gate startup on `db:migrate`, a rolling restart, a break-glass Job beside running
5
+ pods — used to both compare the live schema, plan the same batch, and the
6
+ second failed part way with `42701`/`42P07` "already exists" or a `40P01`
7
+ deadlock. The tracker's per-migration `running` rows cannot prevent that: they
8
+ are written after the diff, when both runs have already planned.
9
+
10
+ ## Contract
11
+
12
+ - A non-dry-run `db:migrate` on PostgreSQL calls core `acquireMigrationLock()`
13
+ right after connecting, before the system-timestamp step, tracker bootstrap,
14
+ and schema comparison, and releases it in the handler's `finally`.
15
+ - The lock is `pg_try_advisory_lock(hashtext('smrt'), hashtext('db:migrate'))`
16
+ (`MIGRATION_ADVISORY_LOCK_KEYS`) on its own `acquireSession()` connection,
17
+ polled once a second. Polling, not a blocking `pg_advisory_lock`, so the
18
+ connection's `lock_timeout` never cuts the wait short and the wait can be
19
+ logged. Never change the keys: runs of an old and a new release must still
20
+ exclude each other.
21
+ - A waiting run logs `Another db:migrate run holds the migration lock…`, then,
22
+ once it holds the lock, compares a current schema and applies nothing.
23
+ - The wait is bounded by `migrations.postgres.migrationLockTimeout` (default
24
+ `15min`; `ms`/`s`/`min`/`h` forms; `0` waits indefinitely). On expiry the run
25
+ fails with `MigrationLockTimeoutError` before reading any schema.
26
+ - The lock dies with its connection: a crashed or killed migrator never strands
27
+ it. `SessionHandle.release()` runs `pg_advisory_unlock_all()` and destroys the
28
+ connection.
29
+ - The flip side: if the lock's own connection dies mid-run (failover, an
30
+ idle-connection reaper, a terminated backend) PostgreSQL frees the lock while
31
+ the run's pool connections carry on. The handler calls `lock.assertHeld()`
32
+ (a `pg_locks` probe on the pinned session) before applying and again before
33
+ the post-apply contract check; a lost lock fails the run with
34
+ `MigrationLockLostError` and exit 1, and the next run converges. The lock
35
+ session sets `idle_session_timeout = 0` (PostgreSQL 14+), because it idles for
36
+ the whole run and a role- or database-level setting would reap it first.
37
+ - Session advisory locks need a real session: a transaction-pooling proxy
38
+ (PgBouncer `pool_mode=transaction`) between `db:migrate` and PostgreSQL
39
+ defeats them. Migrate through a direct or session-pooled connection.
40
+ - `--dry-run` only reads and never waits. SQLite/DuckDB need no lock (no-op).
41
+ A PostgreSQL adapter without `acquireSession()` is refused
42
+ (`MigrationLockUnsupportedError`) rather than run unserialized.
43
+ - The maintenance-window `db:migrate-*` subcommands do not take this lock; run
44
+ them with application migrators stopped.
45
+
46
+ ## Tests
47
+
48
+ - `packages/core/src/migrations/__tests__/migration-lock.test.ts` — contract.
49
+ - `…/migration-lock-postgres.optional.test.ts` — real waits, timeout, release
50
+ on a terminated holder backend (and the holder's `assertHeld()` failing),
51
+ survival under a 1s server `idle_session_timeout`.
52
+ - `packages/cli/src/commands/__tests__/db-migrate-concurrent-postgres.test.ts`
53
+ — two concurrent CLI runs apply one batch; a held lock blocks a run before it
54
+ touches schema. Each run gets its own pool (`getDatabase` caches one pool per
55
+ identity in-process, which two real processes would not share).
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { i as tenantId, r as TenantScoped } from "./dist-wrk3GKo3.js";
2
+ import { i as tenantId, r as TenantScoped } from "./dist-DRSQUACn.js";
3
3
  import { n as __exportAll$1 } from "./NostrIdentity-AC8fAdxe-DKxKyu_a.js";
4
4
  import { SmrtCollection, SmrtObject, field, foreignKey, smrt } from "@happyvertical/smrt-core";
5
5
  //#region ../profiles/dist/chunks/AuditLogCollection-WDHL-_AF.js
@@ -1,5 +1,5 @@
1
1
  import { SmrtObject, field, foreignKey, smrt } from "@happyvertical/smrt-core";
2
- //#region ../profiles/dist/chunks/OidcIdentity-BjRu3n1g.js
2
+ //#region ../profiles/dist/chunks/OidcIdentity-BUblgQLs.js
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
@@ -42,7 +42,7 @@ var OidcIdentity = class extends SmrtObject {
42
42
  * Find identity by issuer and subject
43
43
  */
44
44
  static async findBySubject(issuer, subject, options = {}) {
45
- const { OidcIdentityCollection } = await import("./OidcIdentityCollection-B4jYh36_-6UWtXCwi.js").then((n) => n.r).then((n) => n.r);
45
+ const { OidcIdentityCollection } = await import("./OidcIdentityCollection-ClOyaHeF-DqB9Ex4b.js").then((n) => n.r).then((n) => n.r);
46
46
  return await (await OidcIdentityCollection.create(options)).findBySubject(issuer, subject);
47
47
  }
48
48
  /** Build the collision-free natural key for one OIDC issuer subject. */
@@ -65,7 +65,7 @@ var OidcIdentity = class extends SmrtObject {
65
65
  static async findOrCreate(profile, oidcData, options = {}) {
66
66
  const profileId = profile.id;
67
67
  if (typeof profileId !== "string" || !profileId) throw new Error("OidcIdentity.findOrCreate() requires a saved Profile.");
68
- const { reuseExistingOidcIdentityForProfile } = await import("./resolveIdentity-CvGDegU6-BePPF1Iv.js").then((n) => n.i).then((n) => n.i);
68
+ const { reuseExistingOidcIdentityForProfile } = await import("./resolveIdentity-CkyqvctR-DFVUhcM1.js").then((n) => n.i).then((n) => n.i);
69
69
  const profileOptions = profile.options ?? {};
70
70
  return (await reuseExistingOidcIdentityForProfile(profileId, {
71
71
  email: oidcData.email,
@@ -1,9 +1,9 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
2
  import { n as __exportAll$1 } from "./NostrIdentity-AC8fAdxe-DKxKyu_a.js";
3
- import { t as OidcIdentity } from "./OidcIdentity-BjRu3n1g-CzvSN6Wz.js";
3
+ import { t as OidcIdentity } from "./OidcIdentity-BUblgQLs-B4mzosmT.js";
4
4
  import { SmrtCollection } from "@happyvertical/smrt-core";
5
- //#region ../profiles/dist/chunks/OidcIdentityCollection-B4jYh36_.js
6
- var OidcIdentityCollection_B4jYh36__exports = /* @__PURE__ */ __exportAll({
5
+ //#region ../profiles/dist/chunks/OidcIdentityCollection-ClOyaHeF.js
6
+ var OidcIdentityCollection_ClOyaHeF_exports = /* @__PURE__ */ __exportAll({
7
7
  n: () => OidcIdentityCollection,
8
8
  r: () => OidcIdentityCollection_exports,
9
9
  t: () => AmbiguousOidcIdentityError
@@ -83,4 +83,4 @@ var OidcIdentityCollection = class extends SmrtCollection {
83
83
  }
84
84
  };
85
85
  //#endregion
86
- export { OidcIdentityCollection as n, OidcIdentityCollection_B4jYh36__exports as r, AmbiguousOidcIdentityError as t };
86
+ export { OidcIdentityCollection as n, OidcIdentityCollection_ClOyaHeF_exports as r, AmbiguousOidcIdentityError as t };
@@ -1,10 +1,10 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { i as tenantId, r as TenantScoped } from "./dist-wrk3GKo3.js";
2
+ import { i as tenantId, r as TenantScoped } from "./dist-DRSQUACn.js";
3
3
  import { n as __exportAll$1 } from "./NostrIdentity-AC8fAdxe-DKxKyu_a.js";
4
- import { a as getOwnedAssetsFromCollection, o as removeOwnedAssetFromCollection, t as addOwnedAssetFromCollection } from "./dist-N7UoPf_4.js";
4
+ import { a as getOwnedAssetsFromCollection, o as removeOwnedAssetFromCollection, t as addOwnedAssetFromCollection } from "./dist-DQg_1csT.js";
5
5
  import { SmrtJunction, SmrtObject, crossPackageRef, field, foreignKey, smrt } from "@happyvertical/smrt-core";
6
- //#region ../profiles/dist/chunks/ProfileAssetCollection-HeMtjBc0.js
7
- var ProfileAssetCollection_HeMtjBc0_exports = /* @__PURE__ */ __exportAll({
6
+ //#region ../profiles/dist/chunks/ProfileAssetCollection-CRMcjLJ-.js
7
+ var ProfileAssetCollection_CRMcjLJ__exports = /* @__PURE__ */ __exportAll({
8
8
  n: () => ProfileAssetCollection_exports,
9
9
  r: () => ProfileAsset,
10
10
  t: () => ProfileAssetCollection
@@ -84,7 +84,7 @@ var ProfileAssetCollection = class extends SmrtJunction {
84
84
  profileCollectionPromise = null;
85
85
  async getProfileCollection() {
86
86
  if (!this.profileCollectionPromise) {
87
- const { ProfileCollection } = await import("./ProfileCollection-Bt6IJK49-CFyOiVFz.js").then((n) => n.o).then((n) => n.r);
87
+ const { ProfileCollection } = await import("./ProfileCollection-C4INKh7Y-DU6dPYSG.js").then((n) => n.o).then((n) => n.r);
88
88
  this.profileCollectionPromise = ProfileCollection.create({ db: this.db });
89
89
  }
90
90
  return this.profileCollectionPromise;
@@ -107,4 +107,4 @@ ProfileAssetCollection = __decorateClass([smrt({
107
107
  cli: false
108
108
  })], ProfileAssetCollection);
109
109
  //#endregion
110
- export { ProfileAssetCollection as n, ProfileAssetCollection_HeMtjBc0_exports as r, ProfileAsset as t };
110
+ export { ProfileAssetCollection as n, ProfileAssetCollection_CRMcjLJ__exports as r, ProfileAsset as t };
@@ -1,11 +1,11 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { a as queryGlobal, i as tenantId, l as withSystemContext, n as resolvePrompt, o as queryWithGlobals, r as TenantScoped, s as getTenantId, t as definePrompt } from "./dist-wrk3GKo3.js";
2
+ import { a as queryGlobal, c as getTenantId, i as tenantId, n as resolvePrompt, o as queryWithGlobals, r as TenantScoped, t as definePrompt, u as withSystemContext } from "./dist-DRSQUACn.js";
3
3
  import { n as __exportAll$1 } from "./NostrIdentity-AC8fAdxe-DKxKyu_a.js";
4
- import { a as getOwnedAssetsFromCollection, i as assertValidOwnedAssetSortOrder, n as assertAssetLinkable, o as removeOwnedAssetFromCollection, r as assertValidOwnedAssetRelationship, s as resolveOwnedAssetsById, t as addOwnedAssetFromCollection } from "./dist-N7UoPf_4.js";
4
+ import { a as getOwnedAssetsFromCollection, i as assertValidOwnedAssetSortOrder, n as assertAssetLinkable, o as removeOwnedAssetFromCollection, r as assertValidOwnedAssetRelationship, s as resolveOwnedAssetsById, t as addOwnedAssetFromCollection } from "./dist-DQg_1csT.js";
5
5
  import { RuntimeError, SmrtCollection, SmrtObject, ValidationError, field, foreignKey, isAbortedTransactionError, oneToMany, smrt } from "@happyvertical/smrt-core";
6
6
  import { BackfillTableUnavailableError, BackfillTracker } from "@happyvertical/smrt-core/migrations";
7
- //#region ../profiles/dist/chunks/ProfileCollection-Bt6IJK49.js
8
- var ProfileCollection_Bt6IJK49_exports = /* @__PURE__ */ __exportAll({
7
+ //#region ../profiles/dist/chunks/ProfileCollection-C4INKh7Y.js
8
+ var ProfileCollection_C4INKh7Y_exports = /* @__PURE__ */ __exportAll({
9
9
  _: () => smrtProfilesGenerateBioPrompt,
10
10
  a: () => Profile,
11
11
  d: () => isOidcProvisioningRaceConflict,
@@ -433,6 +433,11 @@ var Profile = class extends SmrtObject {
433
433
  * @returns The slug of the profile type
434
434
  */
435
435
  async getTypeSlug() {
436
+ const tenantId2 = this.tenantId || getTenantId();
437
+ if (this.typeId && tenantId2) {
438
+ const { ProfileTypeCollection } = await import("./ProfileTypeCollection-B7kwi1mU-6KYMumQp.js").then((n) => n.n).then((n) => n.n);
439
+ return (await (await ProfileTypeCollection.create(this.options)).getAvailableById(this.typeId, tenantId2))?.slug ?? "";
440
+ }
436
441
  return (await this.loadRelated("typeId"))?.slug || "";
437
442
  }
438
443
  /**
@@ -453,8 +458,8 @@ var Profile = class extends SmrtObject {
453
458
  * @param value - The value to set
454
459
  */
455
460
  async addMetadata(metafieldSlug, value) {
456
- const { ProfileMetafieldCollection } = await import("./ProfileMetafieldCollection-Buz7gzag-BNRo6e4x.js").then((n) => n.r).then((n) => n.n);
457
- const { ProfileMetadataCollection } = await import("./ProfileMetadataCollection-BIYKy4Y3-CqcaAHAJ.js").then((n) => n.r).then((n) => n.n);
461
+ const { ProfileMetafieldCollection } = await import("./ProfileMetafieldCollection-Buz7gzag-DyUWqbh9.js").then((n) => n.r).then((n) => n.n);
462
+ const { ProfileMetadataCollection } = await import("./ProfileMetadataCollection-BIYKy4Y3-BS9BY4vO.js").then((n) => n.r).then((n) => n.n);
458
463
  const metafield = await this.resolveMetafield(await ProfileMetafieldCollection.create(this.options), metafieldSlug);
459
464
  if (!metafield) throw new Error(`Metafield '${metafieldSlug}' not found`);
460
465
  await metafield.validateValue(value);
@@ -487,7 +492,7 @@ var Profile = class extends SmrtObject {
487
492
  * @returns Object with metafield slugs as keys
488
493
  */
489
494
  async getMetadata() {
490
- const { ProfileMetadataCollection } = await import("./ProfileMetadataCollection-BIYKy4Y3-CqcaAHAJ.js").then((n) => n.r).then((n) => n.n);
495
+ const { ProfileMetadataCollection } = await import("./ProfileMetadataCollection-BIYKy4Y3-BS9BY4vO.js").then((n) => n.r).then((n) => n.n);
491
496
  return await (await ProfileMetadataCollection.create(this.options)).getMetadataObject(this.id);
492
497
  }
493
498
  /**
@@ -504,8 +509,8 @@ var Profile = class extends SmrtObject {
504
509
  * @param metafieldSlug - The slug of the metafield to remove
505
510
  */
506
511
  async removeMetadata(metafieldSlug) {
507
- const { ProfileMetafieldCollection } = await import("./ProfileMetafieldCollection-Buz7gzag-BNRo6e4x.js").then((n) => n.r).then((n) => n.n);
508
- const { ProfileMetadataCollection } = await import("./ProfileMetadataCollection-BIYKy4Y3-CqcaAHAJ.js").then((n) => n.r).then((n) => n.n);
512
+ const { ProfileMetafieldCollection } = await import("./ProfileMetafieldCollection-Buz7gzag-DyUWqbh9.js").then((n) => n.r).then((n) => n.n);
513
+ const { ProfileMetadataCollection } = await import("./ProfileMetadataCollection-BIYKy4Y3-BS9BY4vO.js").then((n) => n.r).then((n) => n.n);
509
514
  const metafield = await this.resolveMetafield(await ProfileMetafieldCollection.create(this.options), metafieldSlug);
510
515
  if (!metafield) throw new Error(`Metafield '${metafieldSlug}' not found`);
511
516
  const existing = await (await ProfileMetadataCollection.create(this.options)).list({
@@ -530,7 +535,7 @@ var Profile = class extends SmrtObject {
530
535
  return candidates.find((candidate) => ownerTenantId && candidate.tenantId === ownerTenantId) ?? candidates.find((candidate) => !candidate.tenantId) ?? null;
531
536
  }
532
537
  async getProfileLinkCollection() {
533
- const { ProfileLinkCollection } = await import("./ProfileLinkCollection-u1hhzKTm-CAfIjZ4f.js").then((n) => n.i).then((n) => n.r);
538
+ const { ProfileLinkCollection } = await import("./ProfileLinkCollection-DFfZRsgv-qtgfWKFC.js").then((n) => n.i).then((n) => n.r);
534
539
  return ProfileLinkCollection.create({ db: this.db });
535
540
  }
536
541
  /** This profile's web links in display order. */
@@ -547,7 +552,7 @@ var Profile = class extends SmrtObject {
547
552
  return (await this.getProfileLinkCollection()).replaceForProfile(this.id, links);
548
553
  }
549
554
  async getProfileAssetCollection() {
550
- const { ProfileAssetCollection } = await import("./ProfileAssetCollection-HeMtjBc0-Cz4MOeSB.js").then((n) => n.r).then((n) => n.n);
555
+ const { ProfileAssetCollection } = await import("./ProfileAssetCollection-CRMcjLJ--CIoZ9Fd2.js").then((n) => n.r).then((n) => n.n);
551
556
  return ProfileAssetCollection.create({ db: this.db });
552
557
  }
553
558
  async getAssets(relationship) {
@@ -579,7 +584,7 @@ var Profile = class extends SmrtObject {
579
584
  */
580
585
  async addRelationship(toProfile, relationshipSlug, contextProfile) {
581
586
  const { ProfileRelationshipTypeCollection } = await import("./ProfileRelationshipTypeCollection-_n22WqTb-DiZUWvev.js").then((n) => n.n).then((n) => n.n);
582
- const { ProfileRelationshipCollection } = await import("./ProfileRelationshipCollection-C4g_QLI4-DeJzJ_hF.js").then((n) => n.r).then((n) => n.n);
587
+ const { ProfileRelationshipCollection } = await import("./ProfileRelationshipCollection-C4g_QLI4-Ci7IHfeW.js").then((n) => n.r).then((n) => n.n);
583
588
  const { ProfileRelationshipType } = await import("./ProfileRelationshipType-U7IwF6Wf-DNavf24e.js").then((n) => n.n).then((n) => n.n);
584
589
  const relationshipType = await (await ProfileRelationshipTypeCollection.create(this.options)).getBySlug(relationshipSlug);
585
590
  if (!relationshipType) throw new Error(`Relationship type '${relationshipSlug}' not found`);
@@ -604,7 +609,7 @@ var Profile = class extends SmrtObject {
604
609
  * @returns Array of ProfileRelationship instances
605
610
  */
606
611
  async getRelationships(options) {
607
- const { ProfileRelationshipCollection } = await import("./ProfileRelationshipCollection-C4g_QLI4-DeJzJ_hF.js").then((n) => n.r).then((n) => n.n);
612
+ const { ProfileRelationshipCollection } = await import("./ProfileRelationshipCollection-C4g_QLI4-Ci7IHfeW.js").then((n) => n.r).then((n) => n.n);
608
613
  const { ProfileRelationshipTypeCollection } = await import("./ProfileRelationshipTypeCollection-_n22WqTb-DiZUWvev.js").then((n) => n.n).then((n) => n.n);
609
614
  const relationshipCollection = await ProfileRelationshipCollection.create(this.options);
610
615
  const direction = options?.direction || "all";
@@ -649,7 +654,7 @@ var Profile = class extends SmrtObject {
649
654
  */
650
655
  async removeRelationship(toProfile, relationshipSlug) {
651
656
  const { ProfileRelationshipTypeCollection } = await import("./ProfileRelationshipTypeCollection-_n22WqTb-DiZUWvev.js").then((n) => n.n).then((n) => n.n);
652
- const { ProfileRelationshipCollection } = await import("./ProfileRelationshipCollection-C4g_QLI4-DeJzJ_hF.js").then((n) => n.r).then((n) => n.n);
657
+ const { ProfileRelationshipCollection } = await import("./ProfileRelationshipCollection-C4g_QLI4-Ci7IHfeW.js").then((n) => n.r).then((n) => n.n);
653
658
  const relationshipType = await (await ProfileRelationshipTypeCollection.create(this.options)).getBySlug(relationshipSlug);
654
659
  if (!relationshipType) throw new Error(`Relationship type '${relationshipSlug}' not found`);
655
660
  const relationshipCollection = await ProfileRelationshipCollection.create(this.options);
@@ -773,7 +778,7 @@ var Profile = class extends SmrtObject {
773
778
  * @returns Array of OIDC identity records
774
779
  */
775
780
  async getOidcIdentities() {
776
- const { OidcIdentityCollection } = await import("./OidcIdentityCollection-B4jYh36_-6UWtXCwi.js").then((n) => n.r).then((n) => n.r);
781
+ const { OidcIdentityCollection } = await import("./OidcIdentityCollection-ClOyaHeF-DqB9Ex4b.js").then((n) => n.r).then((n) => n.r);
777
782
  return await (await OidcIdentityCollection.create(this.options)).findByProfile(this.id);
778
783
  }
779
784
  /**
@@ -786,7 +791,7 @@ var Profile = class extends SmrtObject {
786
791
  * type, but refuses to create or rebind authority.
787
792
  */
788
793
  async linkOidcIdentity(oidcData) {
789
- const { OidcIdentityCollection } = await import("./OidcIdentityCollection-B4jYh36_-6UWtXCwi.js").then((n) => n.r).then((n) => n.r);
794
+ const { OidcIdentityCollection } = await import("./OidcIdentityCollection-ClOyaHeF-DqB9Ex4b.js").then((n) => n.r).then((n) => n.r);
790
795
  return await (await OidcIdentityCollection.create(this.options)).linkToProfile(this, oidcData);
791
796
  }
792
797
  /**
@@ -815,7 +820,7 @@ var Profile = class extends SmrtObject {
815
820
  * @returns Array of audit log entries
816
821
  */
817
822
  async getAuditLogs(limit = 50) {
818
- const { AuditLogCollection } = await import("./AuditLogCollection-WDHL-_AF-G2UOSM3H.js").then((n) => n.r).then((n) => n.n);
823
+ const { AuditLogCollection } = await import("./AuditLogCollection-WDHL-_AF-Bgyxf5sF.js").then((n) => n.r).then((n) => n.n);
819
824
  return await (await AuditLogCollection.create(this.options)).getRecentActivity(this.id, limit);
820
825
  }
821
826
  /**
@@ -825,7 +830,7 @@ var Profile = class extends SmrtObject {
825
830
  * @returns The created audit log entry
826
831
  */
827
832
  async recordAction(options) {
828
- const { AuditLogCollection } = await import("./AuditLogCollection-WDHL-_AF-G2UOSM3H.js").then((n) => n.r).then((n) => n.n);
833
+ const { AuditLogCollection } = await import("./AuditLogCollection-WDHL-_AF-Bgyxf5sF.js").then((n) => n.r).then((n) => n.n);
829
834
  return await (await AuditLogCollection.create(this.options)).record({
830
835
  profile: this,
831
836
  ...options
@@ -1189,4 +1194,4 @@ function readRequiredString(row, key) {
1189
1194
  return value;
1190
1195
  }
1191
1196
  //#endregion
1192
- export { ProfileCollection as a, coordinateOidcProvisioning as c, promptMessageOptions as d, saveOidcRaceArbiter as f, Profile as i, isOidcProvisioningRaceConflict as l, OidcProfileEmailReservation as n, ProfileCollection_Bt6IJK49_exports as o, smrtProfilesGenerateBioPrompt as p, OidcProfileEmailReservationCollection as r, ProfileType as s, CanonicalPersonProfileError as t, normalizeIdentityEmail as u };
1197
+ export { ProfileCollection as a, coordinateOidcProvisioning as c, promptMessageOptions as d, saveOidcRaceArbiter as f, Profile as i, isOidcProvisioningRaceConflict as l, OidcProfileEmailReservation as n, ProfileCollection_C4INKh7Y_exports as o, smrtProfilesGenerateBioPrompt as p, OidcProfileEmailReservationCollection as r, ProfileType as s, CanonicalPersonProfileError as t, normalizeIdentityEmail as u };
@@ -1,9 +1,9 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { i as tenantId, r as TenantScoped } from "./dist-wrk3GKo3.js";
2
+ import { i as tenantId, r as TenantScoped } from "./dist-DRSQUACn.js";
3
3
  import { n as __exportAll$1 } from "./NostrIdentity-AC8fAdxe-DKxKyu_a.js";
4
4
  import { SmrtCollection, SmrtObject, field, foreignKey, isEmbeddedDatabase, smrt, withEmbeddedWriteTransaction } from "@happyvertical/smrt-core";
5
- //#region ../profiles/dist/chunks/ProfileLinkCollection-u1hhzKTm.js
6
- var ProfileLinkCollection_u1hhzKTm_exports = /* @__PURE__ */ __exportAll({
5
+ //#region ../profiles/dist/chunks/ProfileLinkCollection-DFfZRsgv.js
6
+ var ProfileLinkCollection_DFfZRsgv_exports = /* @__PURE__ */ __exportAll({
7
7
  a: () => PROFILE_LINK_MAX_URL_LENGTH,
8
8
  c: () => isProfileLinkPlatform,
9
9
  i: () => 200,
@@ -203,7 +203,7 @@ var ProfileLinkCollection = class ProfileLinkCollection extends SmrtCollection {
203
203
  /** The profile's tenant; throws when the profile is not visible here. */
204
204
  async requireProfileTenant(profileId) {
205
205
  if (!profileId) throw new Error("A profile link needs a profile");
206
- const { ProfileCollection } = await import("./ProfileCollection-Bt6IJK49-CFyOiVFz.js").then((n) => n.o).then((n) => n.r);
206
+ const { ProfileCollection } = await import("./ProfileCollection-C4INKh7Y-DU6dPYSG.js").then((n) => n.o).then((n) => n.r);
207
207
  const profile = await (await ProfileCollection.create({ db: this.db })).get({ id: profileId });
208
208
  if (!profile) throw new Error(`Profile '${profileId}' not found`);
209
209
  return profile.tenantId ?? null;
@@ -238,4 +238,4 @@ function normalizeInputs(links) {
238
238
  return out;
239
239
  }
240
240
  //#endregion
241
- export { isProfileLinkPlatform as a, ProfileLinkCollection_u1hhzKTm_exports as i, ProfileLink as n, normalizeProfileLinkUrl as o, ProfileLinkCollection as r, PROFILE_LINK_PLATFORMS as t };
241
+ export { isProfileLinkPlatform as a, ProfileLinkCollection_DFfZRsgv_exports as i, ProfileLink as n, normalizeProfileLinkUrl as o, ProfileLinkCollection as r, PROFILE_LINK_PLATFORMS as t };
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { i as tenantId, r as TenantScoped } from "./dist-wrk3GKo3.js";
2
+ import { i as tenantId, r as TenantScoped } from "./dist-DRSQUACn.js";
3
3
  import { n as __exportAll$1 } from "./NostrIdentity-AC8fAdxe-DKxKyu_a.js";
4
4
  import { SmrtCollection, SmrtObject, field, foreignKey, smrt } from "@happyvertical/smrt-core";
5
5
  //#region ../profiles/dist/chunks/ProfileMetadataCollection-BIYKy4Y3.js
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { i as tenantId, r as TenantScoped } from "./dist-wrk3GKo3.js";
2
+ import { i as tenantId, r as TenantScoped } from "./dist-DRSQUACn.js";
3
3
  import { n as __exportAll$1 } from "./NostrIdentity-AC8fAdxe-DKxKyu_a.js";
4
4
  import { SmrtCollection, SmrtObject, field, smrt } from "@happyvertical/smrt-core";
5
5
  //#region ../profiles/dist/chunks/ProfileMetafieldCollection-Buz7gzag.js
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
- import { i as tenantId, r as TenantScoped } from "./dist-wrk3GKo3.js";
2
+ import { i as tenantId, r as TenantScoped } from "./dist-DRSQUACn.js";
3
3
  import { n as __exportAll$1 } from "./NostrIdentity-AC8fAdxe-DKxKyu_a.js";
4
4
  import { SmrtCollection, SmrtObject, foreignKey, oneToMany, smrt } from "@happyvertical/smrt-core";
5
5
  //#region ../profiles/dist/chunks/ProfileRelationshipCollection-C4g_QLI4.js
@@ -0,0 +1,103 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
+ import { l as isSystemContext, s as withTenantGlobalRead } from "./dist-DRSQUACn.js";
3
+ import { n as __exportAll$1 } from "./NostrIdentity-AC8fAdxe-DKxKyu_a.js";
4
+ import { s as ProfileType } from "./ProfileCollection-C4INKh7Y-DU6dPYSG.js";
5
+ import { SmrtCollection } from "@happyvertical/smrt-core";
6
+ //#region ../profiles/dist/chunks/ProfileTypeCollection-B7kwi1mU.js
7
+ var ProfileTypeCollection_B7kwi1mU_exports = /* @__PURE__ */ __exportAll({
8
+ n: () => ProfileTypeCollection_exports,
9
+ t: () => ProfileTypeCollection
10
+ });
11
+ var ProfileTypeCollection_exports = /* @__PURE__ */ __exportAll$1({ ProfileTypeCollection: () => ProfileTypeCollection });
12
+ function isPostgresUrl(url) {
13
+ return /^postgres(?:ql)?:/iu.test(url ?? "");
14
+ }
15
+ var ProfileTypeCollection = class extends SmrtCollection {
16
+ static _itemClass = ProfileType;
17
+ /**
18
+ * Get profile type by slug
19
+ *
20
+ * @param slug - The slug to search for
21
+ * @returns ProfileType instance or null
22
+ */
23
+ async getBySlug(slug) {
24
+ return await this.get({ slug });
25
+ }
26
+ /**
27
+ * Resolve one profile type available to a tenant.
28
+ *
29
+ * Profile types may be tenant-owned or global lookup rows. A normal point
30
+ * read under an active tenant context intentionally cannot see a global
31
+ * row, so this uses the bounded tenant-plus-globals list capability. The
32
+ * capability preserves the active actor and fails closed when the requested
33
+ * tenant differs from the active tenant context.
34
+ */
35
+ async getAvailableById(id, tenantId) {
36
+ return (await withTenantGlobalRead(tenantId, () => this.list({
37
+ where: { id },
38
+ limit: 1
39
+ })))[0] ?? null;
40
+ }
41
+ /**
42
+ * Get or create a profile type by slug
43
+ *
44
+ * @param slug - The slug to search for
45
+ * @param defaults - Default values if creating
46
+ * @returns ProfileType instance
47
+ */
48
+ async getOrCreateBySlug(slug, defaults) {
49
+ const existing = await this.getBySlug(slug);
50
+ if (existing) return existing;
51
+ const profileType = await this.create({
52
+ slug,
53
+ ...defaults
54
+ });
55
+ await profileType.save();
56
+ return profileType;
57
+ }
58
+ /**
59
+ * Atomically create or load a global ProfileType from trusted system code.
60
+ *
61
+ * This intentionally bypasses tenant auto-population and therefore refuses
62
+ * to run outside an explicit `withSystemContext()` boundary.
63
+ */
64
+ async getOrCreateGlobalBySlug(slug, defaults) {
65
+ if (!isSystemContext()) throw new Error("Global ProfileType creation requires an explicit system context.");
66
+ const existing = await this.loadGlobalBySlug(slug);
67
+ if (existing) return existing;
68
+ const insertIfAbsent = async (db) => {
69
+ if (isPostgresUrl(db.url)) await db.query("SELECT pg_advisory_xact_lock(hashtext(?), hashtext(?))", "@happyvertical/smrt-profiles:profile_types:global", slug);
70
+ await db.query(`INSERT INTO profile_types
71
+ (id, slug, context, _meta_type, tenant_id, name, description)
72
+ SELECT ?, ?, '', '@happyvertical/smrt-profiles:ProfileType', NULL, ?, ?
73
+ WHERE NOT EXISTS (
74
+ SELECT 1 FROM profile_types
75
+ WHERE slug = ?
76
+ AND context = ''
77
+ AND _meta_type = '@happyvertical/smrt-profiles:ProfileType'
78
+ AND tenant_id IS NULL
79
+ )`, crypto.randomUUID(), slug, defaults.name, defaults.description ?? null, slug);
80
+ };
81
+ const isTransactionBound = typeof this.db.commit === "function";
82
+ if (isPostgresUrl(this.db.url) && !isTransactionBound && typeof this.db.transaction === "function") await this.db.transaction((tx) => insertIfAbsent(tx));
83
+ else await insertIfAbsent(this.db);
84
+ const profileType = await this.loadGlobalBySlug(slug);
85
+ if (!profileType) throw new Error(`Global ProfileType '${slug}' could not be created or loaded.`);
86
+ return profileType;
87
+ }
88
+ async loadGlobalBySlug(slug) {
89
+ const id = (await this.db.query(`SELECT CAST(id AS VARCHAR) AS id
90
+ FROM profile_types
91
+ WHERE slug = ?
92
+ AND context = ''
93
+ AND _meta_type = '@happyvertical/smrt-profiles:ProfileType'
94
+ AND tenant_id IS NULL
95
+ LIMIT 1`, slug)).rows[0]?.id;
96
+ if (typeof id !== "string") return null;
97
+ const profileType = await this.get({ id });
98
+ if (profileType) profileType.id = id;
99
+ return profileType;
100
+ }
101
+ };
102
+ //#endregion
103
+ export { ProfileTypeCollection_B7kwi1mU_exports as n, ProfileTypeCollection as t };
@@ -1 +1 @@
1
- {"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../../src/commands/utilities.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAiJtD,wBAAgB,uBAAuB,CAAC,OAAO,SAAgB,GAAG,MAAM,CAIvE;AA4LD,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;GAIG;AACH,OAAO,EAAE,iCAAiC,EAAE,MAAM,mCAAmC,CAAC;AAEtF;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,OAAO,GAAG,SAAS,EAC1B,KAAK,EAAE,OAAO,GACb,uBAAuB,CA2DzB;AAED,wBAAgB,gCAAgC,CAC9C,eAAe,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EAC9C,qBAAqB,EAAE,QAAQ,CAAC,MAAM,CAAC,GACtC,IAAI,CAcN;AAED;;;GAGG;AACH,UAAU,iBAAiB;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,yDAAyD;AACzD,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC;CACjB;AAmBD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,iBAAiB,GAC7B,MAAM,GAAG,IAAI,CAUf;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,GAAG,0BAA0B,CA+C7B;AA2BD,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,YAAY,GAAG,aAAa,GAAG,UAAU,CAAC;IAChD,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,uEAAuE;AACvE,MAAM,WAAW,kBAAkB;IACjC,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,SAAS,EAAE,iBAAiB,EAAE,CAAC;IAC/B,8DAA8D;IAC9D,SAAS,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1D;;;;;;;;;;;;;;;;;OAiBG;IACH,kBAAkB,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpE;AA4CD;;;;;;;;;;;GAWG;AACH,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAsDzC;AAgBD,mEAAmE;AACnE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,kBAAkB,GAAG,SAAS,GACrC,MAAM,EAAE,CAoCV;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CA09FtD,CAAC"}
1
+ {"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../../src/commands/utilities.ts"],"names":[],"mappings":"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"}