@happyvertical/smrt-profiles 0.33.1 → 0.34.1

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.
@@ -68,7 +68,7 @@ let ProfileAssetCollection = class extends SmrtJunction {
68
68
  profileCollectionPromise = null;
69
69
  async getProfileCollection() {
70
70
  if (!this.profileCollectionPromise) {
71
- const { ProfileCollection } = await import("./ProfileCollection-DU6wUJTO.js").then((n) => n.d);
71
+ const { ProfileCollection } = await import("./ProfileCollection-DQD1uJEc.js").then((n) => n.d);
72
72
  this.profileCollectionPromise = ProfileCollection.create({ db: this.db });
73
73
  }
74
74
  return this.profileCollectionPromise;
@@ -119,4 +119,4 @@ export {
119
119
  ProfileAssetCollection as a,
120
120
  ProfileAssetCollection$1 as b
121
121
  };
122
- //# sourceMappingURL=ProfileAssetCollection-D_tk1kKG.js.map
122
+ //# sourceMappingURL=ProfileAssetCollection-ChX4kLjN.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProfileAssetCollection-D_tk1kKG.js","sources":["../../src/models/ProfileAsset.ts","../../src/collections/ProfileAssetCollection.ts"],"sourcesContent":["import type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\n\nexport interface ProfileAssetOptions extends SmrtObjectOptions {\n profileId?: string;\n assetId?: string;\n relationship?: string;\n sortOrder?: number;\n tenantId?: string | null;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'profile_assets',\n conflictColumns: ['profile_id', 'asset_id', 'relationship'],\n api: false,\n mcp: false,\n cli: false,\n})\nexport class ProfileAsset extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @foreignKey('Profile', { required: true })\n profileId = '';\n\n @crossPackageRef('@happyvertical/smrt-assets:Asset', { required: true })\n assetId = '';\n\n @field({ required: true })\n relationship = 'attachment';\n\n @field()\n sortOrder = 0;\n\n constructor(options: ProfileAssetOptions = {}) {\n super(options);\n if (options.profileId) this.profileId = options.profileId;\n if (options.assetId) this.assetId = options.assetId;\n if (options.relationship) this.relationship = options.relationship;\n if (options.sortOrder !== undefined) this.sortOrder = options.sortOrder;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n }\n}\n","import type { Asset } from '@happyvertical/smrt-assets';\nimport {\n addOwnedAssetFromCollection,\n getOwnedAssetsFromCollection,\n removeOwnedAssetFromCollection,\n} from '@happyvertical/smrt-assets';\nimport type { SmrtCollectionOptions } from '@happyvertical/smrt-core';\nimport { SmrtJunction, smrt } from '@happyvertical/smrt-core';\nimport { ProfileAsset } from '../models/ProfileAsset';\nimport type { ProfileCollection } from './ProfileCollection';\n\nexport interface ProfileAssetCollectionOptions extends SmrtCollectionOptions {}\n\n@smrt({\n api: false,\n mcp: false,\n cli: false,\n})\nexport class ProfileAssetCollection extends SmrtJunction<ProfileAsset> {\n static readonly _itemClass = ProfileAsset;\n protected leftField = 'profileId';\n protected rightField = 'assetId';\n\n private profileCollectionPromise: Promise<ProfileCollection> | null = null;\n\n private async getProfileCollection(): Promise<ProfileCollection> {\n if (!this.profileCollectionPromise) {\n const { ProfileCollection } = await import('./ProfileCollection');\n this.profileCollectionPromise = ProfileCollection.create({ db: this.db });\n }\n\n return this.profileCollectionPromise;\n }\n\n async getAssets(profileId: string, relationship?: string): Promise<Asset[]> {\n return getOwnedAssetsFromCollection(\n await this.getProfileCollection(),\n profileId,\n relationship,\n );\n }\n\n async addAsset(\n profileId: string,\n asset: Asset,\n relationship = 'attachment',\n sortOrder = 0,\n ): Promise<void> {\n await addOwnedAssetFromCollection(\n await this.getProfileCollection(),\n 'Profile',\n profileId,\n asset,\n relationship,\n sortOrder,\n );\n }\n\n async removeAsset(\n profileId: string,\n assetId: string,\n relationship?: string,\n ): Promise<void> {\n await removeOwnedAssetFromCollection(\n await this.getProfileCollection(),\n 'Profile',\n profileId,\n assetId,\n relationship,\n );\n }\n}\n"],"names":["__decorateClass"],"mappings":";;;;;;;;;;;;;AA0BO,IAAM,eAAN,cAA2B,WAAW;AAAA,EAE3C,WAA0B;AAAA,EAG1B,YAAY;AAAA,EAGZ,UAAU;AAAA,EAGV,eAAe;AAAA,EAGf,YAAY;AAAA,EAEZ,YAAY,UAA+B,IAAI;AAC7C,UAAM,OAAO;AACb,QAAI,QAAQ,UAAW,MAAK,YAAY,QAAQ;AAChD,QAAI,QAAQ,QAAS,MAAK,UAAU,QAAQ;AAC5C,QAAI,QAAQ,aAAc,MAAK,eAAe,QAAQ;AACtD,QAAI,QAAQ,cAAc,OAAW,MAAK,YAAY,QAAQ;AAC9D,QAAI,QAAQ,aAAa,OAAW,MAAK,WAAW,QAAQ;AAAA,EAC9D;AACF;AAtBEA,kBAAA;AAAA,EADC,SAAS,EAAE,UAAU,KAAA,CAAM;AAAA,GADjB,aAEX,WAAA,YAAA,CAAA;AAGAA,kBAAA;AAAA,EADC,WAAW,WAAW,EAAE,UAAU,MAAM;AAAA,GAJ9B,aAKX,WAAA,aAAA,CAAA;AAGAA,kBAAA;AAAA,EADC,gBAAgB,oCAAoC,EAAE,UAAU,MAAM;AAAA,GAP5D,aAQX,WAAA,WAAA,CAAA;AAGAA,kBAAA;AAAA,EADC,MAAM,EAAE,UAAU,KAAA,CAAM;AAAA,GAVd,aAWX,WAAA,gBAAA,CAAA;AAGAA,kBAAA;AAAA,EADC,MAAA;AAAM,GAbI,aAcX,WAAA,aAAA,CAAA;AAdW,eAANA,kBAAA;AAAA,EARN,aAAa,EAAE,MAAM,YAAY;AAAA,EACjC,KAAK;AAAA,IACJ,WAAW;AAAA,IACX,iBAAiB,CAAC,cAAc,YAAY,cAAc;AAAA,IAC1D,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EAAA,CACN;AAAA,GACY,YAAA;;;;;;;;;;;;ACRN,IAAM,yBAAN,cAAqC,aAA2B;AAAA,EAE3D,YAAY;AAAA,EACZ,aAAa;AAAA,EAEf,2BAA8D;AAAA,EAEtE,MAAc,uBAAmD;AAC/D,QAAI,CAAC,KAAK,0BAA0B;AAClC,YAAM,EAAE,kBAAA,IAAsB,MAAM,OAAO,iCAAqB,EAAA,KAAA,OAAA,EAAA,CAAA;AAChE,WAAK,2BAA2B,kBAAkB,OAAO,EAAE,IAAI,KAAK,IAAI;AAAA,IAC1E;AAEA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,UAAU,WAAmB,cAAyC;AAC1E,WAAO;AAAA,MACL,MAAM,KAAK,qBAAA;AAAA,MACX;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,MAAM,SACJ,WACA,OACA,eAAe,cACf,YAAY,GACG;AACf,UAAM;AAAA,MACJ,MAAM,KAAK,qBAAA;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,MAAM,YACJ,WACA,SACA,cACe;AACf,UAAM;AAAA,MACJ,MAAM,KAAK,qBAAA;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AACF;AApDE,cADW,wBACK,cAAa,YAAA;AADlB,yBAAN,gBAAA;AAAA,EALN,KAAK;AAAA,IACJ,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EAAA,CACN;AAAA,GACY,sBAAA;;;;;;;"}
1
+ {"version":3,"file":"ProfileAssetCollection-ChX4kLjN.js","sources":["../../src/models/ProfileAsset.ts","../../src/collections/ProfileAssetCollection.ts"],"sourcesContent":["import type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport {\n crossPackageRef,\n field,\n foreignKey,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\n\nexport interface ProfileAssetOptions extends SmrtObjectOptions {\n profileId?: string;\n assetId?: string;\n relationship?: string;\n sortOrder?: number;\n tenantId?: string | null;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableName: 'profile_assets',\n conflictColumns: ['profile_id', 'asset_id', 'relationship'],\n api: false,\n mcp: false,\n cli: false,\n})\nexport class ProfileAsset extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n @foreignKey('Profile', { required: true })\n profileId = '';\n\n @crossPackageRef('@happyvertical/smrt-assets:Asset', { required: true })\n assetId = '';\n\n @field({ required: true })\n relationship = 'attachment';\n\n @field()\n sortOrder = 0;\n\n constructor(options: ProfileAssetOptions = {}) {\n super(options);\n if (options.profileId) this.profileId = options.profileId;\n if (options.assetId) this.assetId = options.assetId;\n if (options.relationship) this.relationship = options.relationship;\n if (options.sortOrder !== undefined) this.sortOrder = options.sortOrder;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n }\n}\n","import type { Asset } from '@happyvertical/smrt-assets';\nimport {\n addOwnedAssetFromCollection,\n getOwnedAssetsFromCollection,\n removeOwnedAssetFromCollection,\n} from '@happyvertical/smrt-assets';\nimport type { SmrtCollectionOptions } from '@happyvertical/smrt-core';\nimport { SmrtJunction, smrt } from '@happyvertical/smrt-core';\nimport { ProfileAsset } from '../models/ProfileAsset';\nimport type { ProfileCollection } from './ProfileCollection';\n\nexport interface ProfileAssetCollectionOptions extends SmrtCollectionOptions {}\n\n@smrt({\n api: false,\n mcp: false,\n cli: false,\n})\nexport class ProfileAssetCollection extends SmrtJunction<ProfileAsset> {\n static readonly _itemClass = ProfileAsset;\n protected leftField = 'profileId';\n protected rightField = 'assetId';\n\n private profileCollectionPromise: Promise<ProfileCollection> | null = null;\n\n private async getProfileCollection(): Promise<ProfileCollection> {\n if (!this.profileCollectionPromise) {\n const { ProfileCollection } = await import('./ProfileCollection');\n this.profileCollectionPromise = ProfileCollection.create({ db: this.db });\n }\n\n return this.profileCollectionPromise;\n }\n\n async getAssets(profileId: string, relationship?: string): Promise<Asset[]> {\n return getOwnedAssetsFromCollection(\n await this.getProfileCollection(),\n profileId,\n relationship,\n );\n }\n\n async addAsset(\n profileId: string,\n asset: Asset,\n relationship = 'attachment',\n sortOrder = 0,\n ): Promise<void> {\n await addOwnedAssetFromCollection(\n await this.getProfileCollection(),\n 'Profile',\n profileId,\n asset,\n relationship,\n sortOrder,\n );\n }\n\n async removeAsset(\n profileId: string,\n assetId: string,\n relationship?: string,\n ): Promise<void> {\n await removeOwnedAssetFromCollection(\n await this.getProfileCollection(),\n 'Profile',\n profileId,\n assetId,\n relationship,\n );\n }\n}\n"],"names":["__decorateClass"],"mappings":";;;;;;;;;;;;;AA0BO,IAAM,eAAN,cAA2B,WAAW;AAAA,EAE3C,WAA0B;AAAA,EAG1B,YAAY;AAAA,EAGZ,UAAU;AAAA,EAGV,eAAe;AAAA,EAGf,YAAY;AAAA,EAEZ,YAAY,UAA+B,IAAI;AAC7C,UAAM,OAAO;AACb,QAAI,QAAQ,UAAW,MAAK,YAAY,QAAQ;AAChD,QAAI,QAAQ,QAAS,MAAK,UAAU,QAAQ;AAC5C,QAAI,QAAQ,aAAc,MAAK,eAAe,QAAQ;AACtD,QAAI,QAAQ,cAAc,OAAW,MAAK,YAAY,QAAQ;AAC9D,QAAI,QAAQ,aAAa,OAAW,MAAK,WAAW,QAAQ;AAAA,EAC9D;AACF;AAtBEA,kBAAA;AAAA,EADC,SAAS,EAAE,UAAU,KAAA,CAAM;AAAA,GADjB,aAEX,WAAA,YAAA,CAAA;AAGAA,kBAAA;AAAA,EADC,WAAW,WAAW,EAAE,UAAU,MAAM;AAAA,GAJ9B,aAKX,WAAA,aAAA,CAAA;AAGAA,kBAAA;AAAA,EADC,gBAAgB,oCAAoC,EAAE,UAAU,MAAM;AAAA,GAP5D,aAQX,WAAA,WAAA,CAAA;AAGAA,kBAAA;AAAA,EADC,MAAM,EAAE,UAAU,KAAA,CAAM;AAAA,GAVd,aAWX,WAAA,gBAAA,CAAA;AAGAA,kBAAA;AAAA,EADC,MAAA;AAAM,GAbI,aAcX,WAAA,aAAA,CAAA;AAdW,eAANA,kBAAA;AAAA,EARN,aAAa,EAAE,MAAM,YAAY;AAAA,EACjC,KAAK;AAAA,IACJ,WAAW;AAAA,IACX,iBAAiB,CAAC,cAAc,YAAY,cAAc;AAAA,IAC1D,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EAAA,CACN;AAAA,GACY,YAAA;;;;;;;;;;;;ACRN,IAAM,yBAAN,cAAqC,aAA2B;AAAA,EAE3D,YAAY;AAAA,EACZ,aAAa;AAAA,EAEf,2BAA8D;AAAA,EAEtE,MAAc,uBAAmD;AAC/D,QAAI,CAAC,KAAK,0BAA0B;AAClC,YAAM,EAAE,kBAAA,IAAsB,MAAM,OAAO,iCAAqB,EAAA,KAAA,OAAA,EAAA,CAAA;AAChE,WAAK,2BAA2B,kBAAkB,OAAO,EAAE,IAAI,KAAK,IAAI;AAAA,IAC1E;AAEA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,UAAU,WAAmB,cAAyC;AAC1E,WAAO;AAAA,MACL,MAAM,KAAK,qBAAA;AAAA,MACX;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,MAAM,SACJ,WACA,OACA,eAAe,cACf,YAAY,GACG;AACf,UAAM;AAAA,MACJ,MAAM,KAAK,qBAAA;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,MAAM,YACJ,WACA,SACA,cACe;AACf,UAAM;AAAA,MACJ,MAAM,KAAK,qBAAA;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AACF;AApDE,cADW,wBACK,cAAa,YAAA;AADlB,yBAAN,gBAAA;AAAA,EALN,KAAK;AAAA,IACJ,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EAAA,CACN;AAAA,GACY,sBAAA;;;;;;;"}
@@ -1,7 +1,7 @@
1
1
  import { resolveOwnedAssetsById, assertValidOwnedAssetRelationship, assertValidOwnedAssetSortOrder, getOwnedAssetsFromCollection, addOwnedAssetFromCollection, removeOwnedAssetFromCollection } from "@happyvertical/smrt-assets";
2
2
  import { field, smrt, SmrtObject, foreignKey, oneToMany, SmrtCollection } from "@happyvertical/smrt-core";
3
+ import { tenantId, TenantScoped, queryGlobal, queryWithGlobals } from "@happyvertical/smrt-tenancy";
3
4
  import { definePrompt, resolvePrompt } from "@happyvertical/smrt-prompts";
4
- import { tenantId, TenantScoped } from "@happyvertical/smrt-tenancy";
5
5
  const smrtProfilesGenerateBioPrompt = definePrompt({
6
6
  key: "smrtProfiles.profile.generateBio",
7
7
  template: `Write a short, professional bio for this person.
@@ -206,7 +206,7 @@ let Profile = class extends SmrtObject {
206
206
  }
207
207
  }
208
208
  async getProfileAssetCollection() {
209
- const { ProfileAssetCollection } = await import("./ProfileAssetCollection-D_tk1kKG.js").then((n) => n.b);
209
+ const { ProfileAssetCollection } = await import("./ProfileAssetCollection-ChX4kLjN.js").then((n) => n.b);
210
210
  return ProfileAssetCollection.create({ db: this.db });
211
211
  }
212
212
  async getAssets(relationship) {
@@ -278,11 +278,11 @@ let Profile = class extends SmrtObject {
278
278
  contextProfileId: contextProfile?.id
279
279
  });
280
280
  await relationship.save();
281
- }
282
- if (relationshipType.reciprocal) {
283
- const handler = ProfileRelationshipType.getReciprocalHandler(relationshipSlug);
284
- if (handler) {
285
- await handler(this, toProfile, contextProfile);
281
+ if (relationshipType.reciprocal) {
282
+ const handler = ProfileRelationshipType.getReciprocalHandler(relationshipSlug);
283
+ if (handler) {
284
+ await handler(this, toProfile, contextProfile);
285
+ }
286
286
  }
287
287
  }
288
288
  }
@@ -501,7 +501,7 @@ let Profile = class extends SmrtObject {
501
501
  * @returns Array of OIDC identity records
502
502
  */
503
503
  async getOidcIdentities() {
504
- const { OidcIdentityCollection } = await import("./index-jFtOWsAV.js").then((n) => n.j);
504
+ const { OidcIdentityCollection } = await import("./index-DHLYp075.js").then((n) => n.j);
505
505
  const collection = await OidcIdentityCollection.create(
506
506
  this.options
507
507
  );
@@ -514,7 +514,7 @@ let Profile = class extends SmrtObject {
514
514
  * @returns The linked OIDC identity record
515
515
  */
516
516
  async linkOidcIdentity(oidcData) {
517
- const { OidcIdentityCollection } = await import("./index-jFtOWsAV.js").then((n) => n.j);
517
+ const { OidcIdentityCollection } = await import("./index-DHLYp075.js").then((n) => n.j);
518
518
  const collection = await OidcIdentityCollection.create(
519
519
  this.options
520
520
  );
@@ -746,24 +746,28 @@ class ProfileCollection extends SmrtCollection {
746
746
  return this.list({ where: { tenantId: tenantId2 } });
747
747
  }
748
748
  /**
749
- * Find all global profiles (without a tenant)
749
+ * Find all global profiles (no tenant association).
750
+ *
751
+ * Routes through the shared tenant-global helper so it does not throw under
752
+ * an active tenant context (an explicit `tenant_id IS NULL` filter would be
753
+ * flagged as an isolation violation). (#1600)
750
754
  *
751
755
  * @returns Array of profiles with null tenantId
752
756
  */
753
757
  async findGlobal() {
754
- return this.list({ where: { tenantId: null } });
758
+ return queryGlobal(this);
755
759
  }
756
760
  /**
757
- * Find profiles belonging to a tenant plus all global profiles
761
+ * Find profiles belonging to a tenant plus all global profiles.
762
+ *
763
+ * Fails closed if an active tenant context requests a different tenant's
764
+ * rows; the admin/system path keeps the cross-tenant capability. (#1600)
758
765
  *
759
766
  * @param tenantId - The tenant UUID to include
760
767
  * @returns Array of tenant-specific and global profiles
761
768
  */
762
769
  async findWithGlobals(tenantId2) {
763
- return this.query(
764
- `SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,
765
- [tenantId2]
766
- );
770
+ return queryWithGlobals(this, tenantId2, "Profile.findWithGlobals");
767
771
  }
768
772
  }
769
773
  const ProfileCollection$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
@@ -779,4 +783,4 @@ export {
779
783
  promptMessageOptions as p,
780
784
  smrtProfilesGenerateBioPrompt as s
781
785
  };
782
- //# sourceMappingURL=ProfileCollection-DU6wUJTO.js.map
786
+ //# sourceMappingURL=ProfileCollection-DQD1uJEc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProfileCollection-DQD1uJEc.js","sources":["../../src/prompts.ts","../../src/models/ProfileType.ts","../../src/models/Profile.ts","../../src/collections/ProfileCollection.ts"],"sourcesContent":["/**\n * Prompt registrations for the @happyvertical/smrt-profiles package.\n *\n * Prompts are registered at module-load time via `definePrompt()` so that\n * tenant-aware overrides can be applied at call time via `resolvePrompt()`.\n *\n * Mirrors the pattern used by `@happyvertical/smrt-content` (see\n * `content-prompts.ts`) and `@happyvertical/smrt-facts` (see `prompts.ts`).\n */\n\nimport {\n definePrompt,\n type ResolvedPromptAI,\n} from '@happyvertical/smrt-prompts';\n\n// Bio generation only uses non-PII profile fields (name + description).\n// Email is intentionally NOT passed to the AI provider — see review on PR\n// #1209 — to minimize PII exposure. If a downstream tenant needs email in\n// the bio, they can override the template via PromptOverride.\nexport const smrtProfilesGenerateBioPrompt = definePrompt({\n key: 'smrtProfiles.profile.generateBio',\n template: `Write a short, professional bio for this person.\n\nProfile name: {profileName}\nProfile description: {profileDescription}\n\nReturn only the bio text, with no commentary or surrounding quotation marks.`,\n editable: {\n template: true,\n profile: true,\n model: true,\n params: true,\n },\n});\n\nexport function promptMessageOptions(ai: ResolvedPromptAI) {\n return {\n ...(ai.params || {}),\n ...(ai.model ? { model: ai.model } : {}),\n ...(typeof ai.temperature === 'number'\n ? { temperature: ai.temperature }\n : {}),\n ...(typeof ai.maxTokens === 'number' ? { maxTokens: ai.maxTokens } : {}),\n };\n}\n","/**\n * ProfileType model - Lookup table defining profile types\n *\n * Represents the nature of a profile (e.g., 'human', 'org', 'robot').\n * Uses UUID primary key with unique slug for human-readable lookups.\n */\n\nimport {\n field,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\n\nexport interface ProfileTypeOptions extends SmrtObjectOptions {\n slug?: string;\n name?: string;\n description?: string;\n tenantId?: string | null;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get'] },\n cli: true,\n})\nexport class ProfileType extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n // id: UUID (auto-generated by SmrtObject)\n // slug is inherited from SmrtObject (auto-generated from name)\n @field({ required: true })\n name: string = '';\n\n description?: string;\n\n constructor(options: ProfileTypeOptions = {}) {\n super(options);\n if (options.name) this.name = options.name;\n if (options.description !== undefined)\n this.description = options.description;\n }\n\n /**\n * Convenience method for slug-based lookup\n *\n * @param slug - The slug to search for\n * @returns ProfileType instance or null if not found\n */\n static async getBySlug(_slug: string): Promise<ProfileType | null> {\n // Will be auto-implemented by SMRT\n return null;\n }\n}\n","/**\n * Profile model - Core entity representing any profile type\n *\n * Central table holding all primary entities with type, email, name, and description.\n * Uses UUID primary key with relationships to ProfileType for classification.\n */\n\nimport type { Asset } from '@happyvertical/smrt-assets';\nimport {\n assertValidOwnedAssetRelationship,\n assertValidOwnedAssetSortOrder,\n resolveOwnedAssetsById,\n} from '@happyvertical/smrt-assets';\n\nimport {\n field,\n foreignKey,\n oneToMany,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { resolvePrompt } from '@happyvertical/smrt-prompts';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n promptMessageOptions,\n smrtProfilesGenerateBioPrompt,\n} from '../prompts';\nimport type { ProfileRelationship } from './ProfileRelationship';\nimport { ProfileType } from './ProfileType';\n\nexport interface ProfileOptions extends SmrtObjectOptions {\n typeId?: string;\n email?: string;\n name?: string;\n description?: string;\n tenantId?: string | null;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get', 'create', 'update'] },\n cli: true,\n})\nexport class Profile extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n // id: UUID (auto-generated by SmrtObject)\n @foreignKey('ProfileType', { required: true })\n typeId?: string; // References ProfileType.id\n\n @field({ unique: true })\n email?: string; // Optional email address\n\n @field({ required: true })\n name: string = ''; // Display name\n\n description?: string; // Short bio or description\n\n // Relationships (not stored as columns)\n @oneToMany('ProfileMetadata')\n metadata: any[] = [];\n\n // ProfileRelationship declares multiple foreign keys back to Profile\n // (fromProfileId / toProfileId / contextProfileId), so each oneToMany names\n // its inverse side explicitly. This both disambiguates `loadRelatedMany`\n // and gives the R10-generated `getRelationshipsFrom()` / `getRelationshipsTo()`\n // accessors the correct inverse foreign key.\n @oneToMany('ProfileRelationship', { foreignKey: 'fromProfileId' })\n relationshipsFrom: any[] = [];\n\n @oneToMany('ProfileRelationship', { foreignKey: 'toProfileId' })\n relationshipsTo: any[] = [];\n\n constructor(options: ProfileOptions = {}) {\n super(options);\n if (options.typeId !== undefined) this.typeId = options.typeId;\n if (options.email !== undefined) this.email = options.email;\n if (options.name) this.name = options.name;\n if (options.description !== undefined)\n this.description = options.description;\n }\n\n /**\n * Get the profile type slug for this profile\n *\n * @returns The slug of the profile type\n */\n async getTypeSlug(): Promise<string> {\n const type = await this.loadRelated('typeId');\n return type?.slug || '';\n }\n\n /**\n * Set the profile type by slug\n *\n * @param slug - The slug of the profile type\n * @throws Error if profile type not found\n */\n async setTypeBySlug(slug: string): Promise<void> {\n const type = await ProfileType.getBySlug(slug);\n if (!type) throw new Error(`Profile type '${slug}' not found`);\n this.typeId = type.id as string;\n }\n\n /**\n * Add metadata to this profile\n *\n * @param metafieldSlug - The slug of the metafield\n * @param value - The value to set\n */\n async addMetadata(metafieldSlug: string, value: any): Promise<void> {\n const { ProfileMetafieldCollection } = await import(\n '../collections/ProfileMetafieldCollection'\n );\n const { ProfileMetadataCollection } = await import(\n '../collections/ProfileMetadataCollection'\n );\n\n // Get or create metafield collection\n const metafieldCollection = await (\n ProfileMetafieldCollection as any\n ).create(this.options);\n\n // Find the metafield by slug\n const metafield = await metafieldCollection.getBySlug(metafieldSlug);\n if (!metafield) {\n throw new Error(`Metafield '${metafieldSlug}' not found`);\n }\n\n // Validate the value\n await metafield.validateValue(value);\n\n // Get or create metadata collection\n const metadataCollection = await (ProfileMetadataCollection as any).create(\n this.options,\n );\n\n // Check if metadata already exists\n const existing = await metadataCollection.list({\n where: { profileId: this.id, metafieldId: metafield.id },\n limit: 1,\n });\n\n if (existing.length > 0) {\n // Update existing\n const metadata = existing[0];\n metadata.value = String(value);\n await metadata.save();\n } else {\n // Create new\n const metadata = await metadataCollection.create({\n profileId: this.id,\n metafieldId: metafield.id,\n value: String(value),\n });\n await metadata.save();\n }\n }\n\n /**\n * Get all metadata for this profile as key-value object\n *\n * @returns Object with metafield slugs as keys\n */\n async getMetadata(): Promise<Record<string, any>> {\n const { ProfileMetadataCollection } = await import(\n '../collections/ProfileMetadataCollection'\n );\n\n const metadataCollection = await (ProfileMetadataCollection as any).create(\n this.options,\n );\n\n return await metadataCollection.getMetadataObject(this.id);\n }\n\n /**\n * Update multiple metadata values\n *\n * @param metadata - Object with metafield slugs as keys and values\n */\n async updateMetadata(metadata: Record<string, any>): Promise<void> {\n for (const [metafieldSlug, value] of Object.entries(metadata)) {\n await this.addMetadata(metafieldSlug, value);\n }\n }\n\n /**\n * Remove metadata by metafield slug\n *\n * @param metafieldSlug - The slug of the metafield to remove\n */\n async removeMetadata(metafieldSlug: string): Promise<void> {\n const { ProfileMetafieldCollection } = await import(\n '../collections/ProfileMetafieldCollection'\n );\n const { ProfileMetadataCollection } = await import(\n '../collections/ProfileMetadataCollection'\n );\n\n const metafieldCollection = await (\n ProfileMetafieldCollection as any\n ).create(this.options);\n\n const metafield = await metafieldCollection.getBySlug(metafieldSlug);\n if (!metafield) {\n throw new Error(`Metafield '${metafieldSlug}' not found`);\n }\n\n const metadataCollection = await (ProfileMetadataCollection as any).create(\n this.options,\n );\n\n const existing = await metadataCollection.list({\n where: { profileId: this.id, metafieldId: metafield.id },\n limit: 1,\n });\n\n if (existing.length > 0) {\n await existing[0].delete();\n }\n }\n\n private async getProfileAssetCollection() {\n const { ProfileAssetCollection } = await import(\n '../collections/ProfileAssetCollection'\n );\n return ProfileAssetCollection.create({ db: this.db });\n }\n async getAssets(relationship?: string): Promise<Asset[]> {\n if (!this.id) {\n return [];\n }\n\n const profileAssets = await this.getProfileAssetCollection();\n const linkedAssets = await profileAssets.byLeft(\n this.id,\n relationship ? { relationship } : {},\n );\n\n return resolveOwnedAssetsById(\n this.db,\n linkedAssets.map((link) => link.assetId),\n this.tenantId,\n );\n }\n\n async addAsset(\n asset: Asset,\n relationship = 'attachment',\n sortOrder = 0,\n ): Promise<void> {\n if (!this.id || !asset.id) {\n throw new Error('Cannot associate unsaved profile or asset');\n }\n\n assertValidOwnedAssetRelationship(relationship);\n assertValidOwnedAssetSortOrder(sortOrder);\n\n const profileAssets = await this.getProfileAssetCollection();\n await profileAssets.attach(this.id, asset.id, {\n relationship,\n sortOrder,\n tenantId: this.tenantId,\n });\n }\n\n async removeAsset(assetId: string, relationship?: string): Promise<void> {\n if (!this.id) {\n return;\n }\n\n const profileAssets = await this.getProfileAssetCollection();\n await profileAssets.detach(\n this.id,\n assetId,\n relationship ? { relationship } : {},\n );\n }\n\n /**\n * Add a relationship to another profile\n *\n * @param toProfile - The target profile\n * @param relationshipSlug - The type of relationship\n * @param contextProfile - Optional context profile for tertiary relationships\n */\n async addRelationship(\n toProfile: Profile,\n relationshipSlug: string,\n contextProfile?: Profile,\n ): Promise<void> {\n const { ProfileRelationshipTypeCollection } = await import(\n '../collections/ProfileRelationshipTypeCollection'\n );\n const { ProfileRelationshipCollection } = await import(\n '../collections/ProfileRelationshipCollection'\n );\n const { ProfileRelationshipType } = await import(\n './ProfileRelationshipType'\n );\n\n // Get relationship type\n const relationshipTypeCollection = await (\n ProfileRelationshipTypeCollection as any\n ).create(this.options);\n\n const relationshipType =\n await relationshipTypeCollection.getBySlug(relationshipSlug);\n if (!relationshipType) {\n throw new Error(`Relationship type '${relationshipSlug}' not found`);\n }\n\n // Check if relationship already exists\n const relationshipCollection = await (\n ProfileRelationshipCollection as any\n ).create(this.options);\n\n const exists = await relationshipCollection.exists(\n this.id,\n toProfile.id,\n relationshipType.id,\n );\n\n if (!exists) {\n // Create the relationship\n const relationship = await relationshipCollection.create({\n fromProfileId: this.id,\n toProfileId: toProfile.id,\n typeId: relationshipType.id,\n contextProfileId: contextProfile?.id,\n });\n await relationship.save();\n\n // Handle reciprocal relationships. A reciprocal handler creates the\n // inverse edge by calling addRelationship() back on `toProfile`. Gating\n // this on `!exists` is what terminates the recursion: the inverse call\n // creates its own edge and re-enters, but by then the original edge\n // already exists, so the handler is skipped and the cycle stops. Firing\n // it unconditionally instead recurses forever (the bundled friend /\n // spouse / partner / colleague / sibling handlers all call back here).\n if (relationshipType.reciprocal) {\n const handler =\n ProfileRelationshipType.getReciprocalHandler(relationshipSlug);\n if (handler) {\n await handler(this, toProfile, contextProfile);\n }\n }\n }\n }\n\n /**\n * Get all relationships for this profile\n *\n * @param options - Filter options (typeSlug, direction)\n * @returns Array of ProfileRelationship instances\n */\n async getRelationships(options?: {\n typeSlug?: string;\n direction?: 'from' | 'to' | 'all';\n }): Promise<ProfileRelationship[]> {\n const { ProfileRelationshipCollection } = await import(\n '../collections/ProfileRelationshipCollection'\n );\n const { ProfileRelationshipTypeCollection } = await import(\n '../collections/ProfileRelationshipTypeCollection'\n );\n\n const relationshipCollection = await (\n ProfileRelationshipCollection as any\n ).create(this.options);\n\n const direction = options?.direction || 'all';\n\n // Get type ID if typeSlug is provided\n let typeId: string | undefined;\n if (options?.typeSlug) {\n const relationshipTypeCollection = await (\n ProfileRelationshipTypeCollection as any\n ).create(this.options);\n\n const relationshipType = await relationshipTypeCollection.getBySlug(\n options.typeSlug,\n );\n typeId = relationshipType?.id;\n }\n\n // Fetch relationships based on direction\n if (direction === 'from') {\n return await relationshipCollection.getFromProfile(this.id, typeId);\n } else if (direction === 'to') {\n return await relationshipCollection.getToProfile(this.id, typeId);\n } else {\n return await relationshipCollection.getForProfile(this.id, typeId);\n }\n }\n\n /**\n * Get related profiles\n *\n * @param relationshipSlug - Optional filter by relationship type slug\n * @returns Array of related Profile instances\n */\n async getRelatedProfiles(relationshipSlug?: string): Promise<Profile[]> {\n const { ProfileCollection } = await import(\n '../collections/ProfileCollection'\n );\n\n const relationships = await this.getRelationships({\n typeSlug: relationshipSlug,\n direction: 'all',\n });\n\n const profileCollection = await (ProfileCollection as any).create(\n this.options,\n );\n\n const relatedProfiles: Profile[] = [];\n const seenIds = new Set<string>();\n\n for (const relationship of relationships) {\n // Get the other profile (not this one)\n // Convert Field instances to strings for comparison\n const fromId = String(relationship.fromProfileId);\n const toId = String(relationship.toProfileId);\n const thisId = String(this.id);\n const otherId = fromId === thisId ? toId : fromId;\n\n if (!seenIds.has(otherId)) {\n seenIds.add(otherId);\n const profile = await profileCollection.get({ id: otherId });\n if (profile) {\n relatedProfiles.push(profile);\n }\n }\n }\n\n return relatedProfiles;\n }\n\n /**\n * Remove a relationship to another profile\n *\n * @param toProfile - The target profile\n * @param relationshipSlug - The type of relationship to remove\n */\n async removeRelationship(\n toProfile: Profile,\n relationshipSlug: string,\n ): Promise<void> {\n const { ProfileRelationshipTypeCollection } = await import(\n '../collections/ProfileRelationshipTypeCollection'\n );\n const { ProfileRelationshipCollection } = await import(\n '../collections/ProfileRelationshipCollection'\n );\n\n // Get relationship type\n const relationshipTypeCollection = await (\n ProfileRelationshipTypeCollection as any\n ).create(this.options);\n\n const relationshipType =\n await relationshipTypeCollection.getBySlug(relationshipSlug);\n if (!relationshipType) {\n throw new Error(`Relationship type '${relationshipSlug}' not found`);\n }\n\n // Find and delete the relationship\n const relationshipCollection = await (\n ProfileRelationshipCollection as any\n ).create(this.options);\n\n const relationships = await relationshipCollection.list({\n where: {\n fromProfileId: this.id,\n toProfileId: toProfile.id,\n typeId: relationshipType.id,\n },\n });\n\n for (const relationship of relationships) {\n await relationship.delete();\n }\n\n // Handle reciprocal relationships - delete the inverse\n if (relationshipType.reciprocal) {\n const inverseRelationships = await relationshipCollection.list({\n where: {\n fromProfileId: toProfile.id,\n toProfileId: this.id,\n typeId: relationshipType.id,\n },\n });\n\n for (const relationship of inverseRelationships) {\n await relationship.delete();\n }\n }\n }\n\n /**\n * AI-powered: Generate a professional bio for this profile\n *\n * Uses the `smrtProfiles.profile.generateBio` prompt registered via\n * `@happyvertical/smrt-prompts`, allowing tenant- or instance-level\n * overrides of the template, model, and parameters at runtime.\n *\n * @returns Generated bio text\n */\n async generateBio(): Promise<string> {\n // Resolve `db` from either the canonical `db` option or its `persistence`\n // alias. SmrtClass maps `persistence → db` lazily during `initialize()`,\n // so on a freshly-constructed Profile that has not yet been initialized,\n // `this.options.db` may be undefined while `this.options.persistence` is\n // set. Falling back here ensures stored app- and tenant-level prompt\n // overrides in `_smrt_prompt_overrides` are honored on the first call —\n // before `getAiClient()` triggers full initialization further below.\n const db = this.options.db ?? this.options.persistence;\n\n const resolvedPrompt = await resolvePrompt(\n smrtProfilesGenerateBioPrompt.key,\n {\n db,\n tenantId: this.tenantId,\n variables: {\n profileName: this.name || '',\n profileDescription: this.description || '',\n },\n },\n );\n\n const ai = await this.getAiClient();\n const response = await ai.message(\n resolvedPrompt.text,\n promptMessageOptions(resolvedPrompt.ai),\n );\n\n return response.trim();\n }\n\n /**\n * AI-powered: Check if profile matches criteria\n *\n * @param criteria - Criteria to match against\n * @returns True if matches criteria\n */\n async matches(criteria: string): Promise<boolean> {\n return await this.is(criteria);\n }\n\n /**\n * Find profiles by metadata key-value pair\n *\n * @param metafieldSlug - The metafield slug to search\n * @param value - The value to match\n * @returns Array of matching profiles\n */\n static async findByMetadata(\n _metafieldSlug: string,\n _value: any,\n ): Promise<Profile[]> {\n // Will be auto-implemented by SMRT\n return [];\n }\n\n /**\n * Find profiles by type slug\n *\n * @param typeSlug - The profile type slug\n * @returns Array of matching profiles\n */\n static async findByType(_typeSlug: string): Promise<Profile[]> {\n // Will be auto-implemented by SMRT\n return [];\n }\n\n /**\n * Find related profiles for a given profile\n *\n * @param profileId - The profile UUID\n * @param relationshipSlug - Optional filter by relationship type\n * @returns Array of related profiles\n */\n static async findRelated(\n _profileId: string,\n _relationshipSlug?: string,\n ): Promise<Profile[]> {\n // Will be auto-implemented by SMRT\n return [];\n }\n\n /**\n * Search profiles by email\n *\n * @param email - The email to search for\n * @returns Profile or null if not found\n */\n static async searchByEmail(_email: string): Promise<Profile | null> {\n // Will be auto-implemented by SMRT\n return null;\n }\n\n // ========================\n // Auth-related methods\n // ========================\n\n /**\n * Get all API keys for this profile\n *\n * @returns Array of API keys\n */\n async getApiKeys(): Promise<any[]> {\n const { ApiKeyCollection } = await import(\n '../collections/ApiKeyCollection'\n );\n const collection = await (ApiKeyCollection as any).create(this.options);\n return await collection.findByProfile(this.id as string);\n }\n\n /**\n * Get active (non-revoked, non-expired) API keys for this profile\n *\n * @returns Array of active API keys\n */\n async getActiveApiKeys(): Promise<any[]> {\n const { ApiKeyCollection } = await import(\n '../collections/ApiKeyCollection'\n );\n const collection = await (ApiKeyCollection as any).create(this.options);\n return await collection.findActiveByProfile(this.id as string);\n }\n\n /**\n * Generate a new API key for this profile\n *\n * @param options - Key options (name, scopes, expiration)\n * @returns The generated key (plaintext) and ApiKey record\n */\n async generateApiKey(options: {\n name: string;\n scopes?: string[];\n expiresAt?: Date | null;\n }): Promise<{ key: string; apiKey: any }> {\n const { ApiKey } = await import('./ApiKey');\n return await ApiKey.generate(this, {\n ...options,\n db: this.options?.db,\n });\n }\n\n /**\n * Get all OIDC identities linked to this profile\n *\n * @returns Array of OIDC identity records\n */\n async getOidcIdentities(): Promise<any[]> {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const collection = await (OidcIdentityCollection as any).create(\n this.options,\n );\n return await collection.findByProfile(this.id as string);\n }\n\n /**\n * Link a new OIDC identity to this profile\n *\n * @param oidcData - OIDC provider data\n * @returns The linked OIDC identity record\n */\n async linkOidcIdentity(oidcData: {\n provider: string;\n issuer: string;\n subject: string;\n email?: string;\n }): Promise<any> {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const collection = await (OidcIdentityCollection as any).create(\n this.options,\n );\n return await collection.linkToProfile(this, oidcData);\n }\n\n /**\n * Get all Nostr identities linked to this profile\n *\n * @returns Array of Nostr identity records\n */\n async getNostrIdentities(): Promise<any[]> {\n const { NostrIdentityCollection } = await import(\n '../collections/NostrIdentityCollection'\n );\n const collection = await (NostrIdentityCollection as any).create(\n this.options,\n );\n return await collection.findByProfile(this.id as string);\n }\n\n /**\n * Link a new Nostr identity to this profile\n *\n * @param nostrData - Nostr identity data (encrypted keypair)\n * @returns The linked Nostr identity record\n */\n async linkNostrIdentity(nostrData: {\n pubkey: string;\n encryptedPrivkey: string;\n encryptionIv: string;\n encryptionTag: string;\n email: string;\n nip05Username?: string;\n }): Promise<any> {\n const { NostrIdentityCollection } = await import(\n '../collections/NostrIdentityCollection'\n );\n const collection = await (NostrIdentityCollection as any).create(\n this.options,\n );\n return await collection.linkToProfile(this, nostrData);\n }\n\n /**\n * Get audit logs for actions performed by this profile\n *\n * @param limit - Maximum number of logs to return\n * @returns Array of audit log entries\n */\n async getAuditLogs(limit: number = 50): Promise<any[]> {\n const { AuditLogCollection } = await import(\n '../collections/AuditLogCollection'\n );\n const collection = await (AuditLogCollection as any).create(this.options);\n return await collection.getRecentActivity(this.id as string, limit);\n }\n\n /**\n * Record an audit log entry for an action by this profile\n *\n * @param options - Audit log options\n * @returns The created audit log entry\n */\n async recordAction(options: {\n action: string;\n resourceType: string;\n resourceId: string;\n source?: 'web' | 'cli' | 'ci' | 'webhook' | 'mcp';\n metadata?: Record<string, any>;\n onBehalfOf?: Profile | null;\n }): Promise<any> {\n const { AuditLogCollection } = await import(\n '../collections/AuditLogCollection'\n );\n const collection = await (AuditLogCollection as any).create(this.options);\n return await collection.record({\n profile: this,\n ...options,\n });\n }\n}\n","/**\n * ProfileCollection - Collection manager for Profile objects\n *\n * Provides advanced querying and batch operations for Profile entities.\n */\n\nimport type { Asset } from '@happyvertical/smrt-assets';\nimport {\n addOwnedAssetFromCollection,\n getOwnedAssetsFromCollection,\n removeOwnedAssetFromCollection,\n} from '@happyvertical/smrt-assets';\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { queryGlobal, queryWithGlobals } from '@happyvertical/smrt-tenancy';\nimport { Profile } from '../models/Profile';\n\nexport class ProfileCollection extends SmrtCollection<Profile> {\n static readonly _itemClass = Profile;\n\n /**\n * Find a profile by email address\n *\n * @param email - The email address to search for\n * @returns The matching profile or null\n */\n async findByEmail(email: string): Promise<Profile | null> {\n const normalizedEmail = email.toLowerCase();\n const profiles = await this.list({\n where: { email: normalizedEmail },\n limit: 1,\n });\n return profiles.length > 0 ? profiles[0] : null;\n }\n\n /**\n * Find profiles by type slug\n *\n * @param typeSlug - The profile type slug to filter by\n * @returns Array of matching profiles\n */\n async findByType(typeSlug: string): Promise<Profile[]> {\n // Will use eager loading when available\n const allProfiles = await this.list({});\n\n const filtered: Profile[] = [];\n for (const profile of allProfiles) {\n const slug = await profile.getTypeSlug();\n if (slug === typeSlug) {\n filtered.push(profile);\n }\n }\n\n return filtered;\n }\n\n /**\n * Batch get metadata for multiple profiles\n *\n * @param profileIds - Array of profile UUIDs\n * @returns Map of profile ID to metadata object\n */\n async batchGetMetadata(\n profileIds: string[],\n ): Promise<Map<string, Record<string, any>>> {\n const result = new Map<string, Record<string, any>>();\n\n for (const profileId of profileIds) {\n const profile = await this.get({ id: profileId });\n if (profile) {\n const metadata = await profile.getMetadata();\n result.set(profileId, metadata);\n }\n }\n\n return result;\n }\n\n /**\n * Batch update metadata for multiple profiles\n *\n * @param updates - Array of { profileId, data } objects\n */\n async batchUpdateMetadata(\n updates: Array<{ profileId: string; data: Record<string, any> }>,\n ): Promise<void> {\n for (const update of updates) {\n const profile = await this.get({ id: update.profileId });\n if (profile) {\n await profile.updateMetadata(update.data);\n }\n }\n }\n\n /**\n * Find related profiles for a given profile\n *\n * @param profileId - The profile UUID\n * @param relationshipSlug - Optional filter by relationship type\n * @returns Array of related profiles\n */\n async findRelated(\n profileId: string,\n relationshipSlug?: string,\n ): Promise<Profile[]> {\n const profile = await this.get({ id: profileId });\n if (!profile) return [];\n\n return await profile.getRelatedProfiles(relationshipSlug);\n }\n\n async getAssets(profileId: string, relationship?: string): Promise<Asset[]> {\n return getOwnedAssetsFromCollection(this, profileId, relationship);\n }\n\n async addAsset(\n profileId: string,\n asset: Asset,\n relationship = 'attachment',\n sortOrder = 0,\n ): Promise<void> {\n await addOwnedAssetFromCollection(\n this,\n 'Profile',\n profileId,\n asset,\n relationship,\n sortOrder,\n );\n }\n\n async removeAsset(\n profileId: string,\n assetId: string,\n relationship?: string,\n ): Promise<void> {\n await removeOwnedAssetFromCollection(\n this,\n 'Profile',\n profileId,\n assetId,\n relationship,\n );\n }\n\n /**\n * Get the relationship network for a profile up to a maximum depth\n *\n * @param profileId - The starting profile UUID\n * @param options - Configuration options\n * @returns Map of profile ID to depth level\n */\n async getRelationshipNetwork(\n profileId: string,\n options: { maxDepth?: number } = {},\n ): Promise<Map<string, number>> {\n const maxDepth = options.maxDepth || 2;\n const network = new Map<string, number>();\n const visited = new Set<string>();\n const queue: Array<{ id: string; depth: number }> = [\n { id: profileId, depth: 0 },\n ];\n\n while (queue.length > 0) {\n const current = queue.shift();\n if (!current) {\n break;\n }\n\n if (visited.has(current.id) || current.depth > maxDepth) {\n continue;\n }\n\n visited.add(current.id);\n network.set(current.id, current.depth);\n\n if (current.depth < maxDepth) {\n const related = await this.findRelated(current.id);\n for (const profile of related) {\n if (profile.id && !visited.has(profile.id)) {\n queue.push({ id: profile.id, depth: current.depth + 1 });\n }\n }\n }\n }\n\n return network;\n }\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Tenant-scoped helper methods\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Find all profiles belonging to a specific tenant\n *\n * @param tenantId - The tenant UUID to filter by\n * @returns Array of profiles for this tenant\n */\n async findByTenant(tenantId: string): Promise<Profile[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global profiles (no tenant association).\n *\n * Routes through the shared tenant-global helper so it does not throw under\n * an active tenant context (an explicit `tenant_id IS NULL` filter would be\n * flagged as an isolation violation). (#1600)\n *\n * @returns Array of profiles with null tenantId\n */\n async findGlobal(): Promise<Profile[]> {\n return queryGlobal<Profile>(this);\n }\n\n /**\n * Find profiles belonging to a tenant plus all global profiles.\n *\n * Fails closed if an active tenant context requests a different tenant's\n * rows; the admin/system path keeps the cross-tenant capability. (#1600)\n *\n * @param tenantId - The tenant UUID to include\n * @returns Array of tenant-specific and global profiles\n */\n async findWithGlobals(tenantId: string): Promise<Profile[]> {\n return queryWithGlobals<Profile>(this, tenantId, 'Profile.findWithGlobals');\n }\n}\n"],"names":["__decorateClass","ProfileCollection","tenantId"],"mappings":";;;;AAmBO,MAAM,gCAAgC,aAAa;AAAA,EACxD,KAAK;AAAA,EACL,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMV,UAAU;AAAA,IACR,UAAU;AAAA,IACV,SAAS;AAAA,IACT,OAAO;AAAA,IACP,QAAQ;AAAA,EAAA;AAEZ,CAAC;AAEM,SAAS,qBAAqB,IAAsB;AACzD,SAAO;AAAA,IACL,GAAI,GAAG,UAAU,CAAA;AAAA,IACjB,GAAI,GAAG,QAAQ,EAAE,OAAO,GAAG,MAAA,IAAU,CAAA;AAAA,IACrC,GAAI,OAAO,GAAG,gBAAgB,WAC1B,EAAE,aAAa,GAAG,YAAA,IAClB,CAAA;AAAA,IACJ,GAAI,OAAO,GAAG,cAAc,WAAW,EAAE,WAAW,GAAG,cAAc,CAAA;AAAA,EAAC;AAE1E;;;;;;;;;;;ACfO,IAAM,cAAN,cAA0B,WAAW;AAAA,EAE1C,WAA0B;AAAA,EAK1B,OAAe;AAAA,EAEf;AAAA,EAEA,YAAY,UAA8B,IAAI;AAC5C,UAAM,OAAO;AACb,QAAI,QAAQ,KAAM,MAAK,OAAO,QAAQ;AACtC,QAAI,QAAQ,gBAAgB;AAC1B,WAAK,cAAc,QAAQ;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAa,UAAU,OAA4C;AAEjE,WAAO;AAAA,EACT;AACF;AA1BEA,kBAAA;AAAA,EADC,SAAS,EAAE,UAAU,KAAA,CAAM;AAAA,GADjB,YAEX,WAAA,YAAA,CAAA;AAKAA,kBAAA;AAAA,EADC,MAAM,EAAE,UAAU,KAAA,CAAM;AAAA,GANd,YAOX,WAAA,QAAA,CAAA;AAPW,cAANA,kBAAA;AAAA,EAPN,aAAa,EAAE,MAAM,YAAY;AAAA,EACjC,KAAK;AAAA,IACJ,eAAe;AAAA,IACf,KAAK,EAAE,SAAS,CAAC,QAAQ,OAAO,UAAU,QAAQ,EAAA;AAAA,IAClD,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAA;AAAA,IAC9B,KAAK;AAAA,EAAA,CACN;AAAA,GACY,WAAA;;;;;;;;;;;;;;;;;ACiBN,IAAM,UAAN,cAAsB,WAAW;AAAA,EAEtC,WAA0B;AAAA,EAI1B;AAAA,EAGA;AAAA,EAGA,OAAe;AAAA;AAAA,EAEf;AAAA,EAIA,WAAkB,CAAA;AAAA,EAQlB,oBAA2B,CAAA;AAAA,EAG3B,kBAAyB,CAAA;AAAA,EAEzB,YAAY,UAA0B,IAAI;AACxC,UAAM,OAAO;AACb,QAAI,QAAQ,WAAW,OAAW,MAAK,SAAS,QAAQ;AACxD,QAAI,QAAQ,UAAU,OAAW,MAAK,QAAQ,QAAQ;AACtD,QAAI,QAAQ,KAAM,MAAK,OAAO,QAAQ;AACtC,QAAI,QAAQ,gBAAgB;AAC1B,WAAK,cAAc,QAAQ;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,cAA+B;AACnC,UAAM,OAAO,MAAM,KAAK,YAAY,QAAQ;AAC5C,WAAO,MAAM,QAAQ;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,cAAc,MAA6B;AAC/C,UAAM,OAAO,MAAM,YAAY,UAAU,IAAI;AAC7C,QAAI,CAAC,KAAM,OAAM,IAAI,MAAM,iBAAiB,IAAI,aAAa;AAC7D,SAAK,SAAS,KAAK;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,YAAY,eAAuB,OAA2B;AAClE,UAAM,EAAE,2BAAA,IAA+B,MAAM,OAC3C,0CACF,EAAA,KAAA,OAAA,EAAA,CAAA;AACA,UAAM,EAAE,0BAAA,IAA8B,MAAM,OAC1C,yCACF,EAAA,KAAA,OAAA,EAAA,CAAA;AAGA,UAAM,sBAAsB,MAC1B,2BACA,OAAO,KAAK,OAAO;AAGrB,UAAM,YAAY,MAAM,oBAAoB,UAAU,aAAa;AACnE,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,MAAM,cAAc,aAAa,aAAa;AAAA,IAC1D;AAGA,UAAM,UAAU,cAAc,KAAK;AAGnC,UAAM,qBAAqB,MAAO,0BAAkC;AAAA,MAClE,KAAK;AAAA,IAAA;AAIP,UAAM,WAAW,MAAM,mBAAmB,KAAK;AAAA,MAC7C,OAAO,EAAE,WAAW,KAAK,IAAI,aAAa,UAAU,GAAA;AAAA,MACpD,OAAO;AAAA,IAAA,CACR;AAED,QAAI,SAAS,SAAS,GAAG;AAEvB,YAAM,WAAW,SAAS,CAAC;AAC3B,eAAS,QAAQ,OAAO,KAAK;AAC7B,YAAM,SAAS,KAAA;AAAA,IACjB,OAAO;AAEL,YAAM,WAAW,MAAM,mBAAmB,OAAO;AAAA,QAC/C,WAAW,KAAK;AAAA,QAChB,aAAa,UAAU;AAAA,QACvB,OAAO,OAAO,KAAK;AAAA,MAAA,CACpB;AACD,YAAM,SAAS,KAAA;AAAA,IACjB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,cAA4C;AAChD,UAAM,EAAE,0BAAA,IAA8B,MAAM,OAC1C,yCACF,EAAA,KAAA,OAAA,EAAA,CAAA;AAEA,UAAM,qBAAqB,MAAO,0BAAkC;AAAA,MAClE,KAAK;AAAA,IAAA;AAGP,WAAO,MAAM,mBAAmB,kBAAkB,KAAK,EAAE;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,eAAe,UAA8C;AACjE,eAAW,CAAC,eAAe,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG;AAC7D,YAAM,KAAK,YAAY,eAAe,KAAK;AAAA,IAC7C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,eAAe,eAAsC;AACzD,UAAM,EAAE,2BAAA,IAA+B,MAAM,OAC3C,0CACF,EAAA,KAAA,OAAA,EAAA,CAAA;AACA,UAAM,EAAE,0BAAA,IAA8B,MAAM,OAC1C,yCACF,EAAA,KAAA,OAAA,EAAA,CAAA;AAEA,UAAM,sBAAsB,MAC1B,2BACA,OAAO,KAAK,OAAO;AAErB,UAAM,YAAY,MAAM,oBAAoB,UAAU,aAAa;AACnE,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,MAAM,cAAc,aAAa,aAAa;AAAA,IAC1D;AAEA,UAAM,qBAAqB,MAAO,0BAAkC;AAAA,MAClE,KAAK;AAAA,IAAA;AAGP,UAAM,WAAW,MAAM,mBAAmB,KAAK;AAAA,MAC7C,OAAO,EAAE,WAAW,KAAK,IAAI,aAAa,UAAU,GAAA;AAAA,MACpD,OAAO;AAAA,IAAA,CACR;AAED,QAAI,SAAS,SAAS,GAAG;AACvB,YAAM,SAAS,CAAC,EAAE,OAAA;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,MAAc,4BAA4B;AACxC,UAAM,EAAE,uBAAA,IAA2B,MAAM,OACvC,sCACF,EAAA,KAAA,OAAA,EAAA,CAAA;AACA,WAAO,uBAAuB,OAAO,EAAE,IAAI,KAAK,IAAI;AAAA,EACtD;AAAA,EACA,MAAM,UAAU,cAAyC;AACvD,QAAI,CAAC,KAAK,IAAI;AACZ,aAAO,CAAA;AAAA,IACT;AAEA,UAAM,gBAAgB,MAAM,KAAK,0BAAA;AACjC,UAAM,eAAe,MAAM,cAAc;AAAA,MACvC,KAAK;AAAA,MACL,eAAe,EAAE,iBAAiB,CAAA;AAAA,IAAC;AAGrC,WAAO;AAAA,MACL,KAAK;AAAA,MACL,aAAa,IAAI,CAAC,SAAS,KAAK,OAAO;AAAA,MACvC,KAAK;AAAA,IAAA;AAAA,EAET;AAAA,EAEA,MAAM,SACJ,OACA,eAAe,cACf,YAAY,GACG;AACf,QAAI,CAAC,KAAK,MAAM,CAAC,MAAM,IAAI;AACzB,YAAM,IAAI,MAAM,2CAA2C;AAAA,IAC7D;AAEA,sCAAkC,YAAY;AAC9C,mCAA+B,SAAS;AAExC,UAAM,gBAAgB,MAAM,KAAK,0BAAA;AACjC,UAAM,cAAc,OAAO,KAAK,IAAI,MAAM,IAAI;AAAA,MAC5C;AAAA,MACA;AAAA,MACA,UAAU,KAAK;AAAA,IAAA,CAChB;AAAA,EACH;AAAA,EAEA,MAAM,YAAY,SAAiB,cAAsC;AACvE,QAAI,CAAC,KAAK,IAAI;AACZ;AAAA,IACF;AAEA,UAAM,gBAAgB,MAAM,KAAK,0BAAA;AACjC,UAAM,cAAc;AAAA,MAClB,KAAK;AAAA,MACL;AAAA,MACA,eAAe,EAAE,iBAAiB,CAAA;AAAA,IAAC;AAAA,EAEvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,gBACJ,WACA,kBACA,gBACe;AACf,UAAM,EAAE,kCAAA,IAAsC,MAAM,OAClD,iDACF;AACA,UAAM,EAAE,8BAAA,IAAkC,MAAM,OAC9C,6CACF,EAAA,KAAA,OAAA,EAAA,CAAA;AACA,UAAM,EAAE,wBAAA,IAA4B,MAAM,OACxC,uCACF;AAGA,UAAM,6BAA6B,MACjC,kCACA,OAAO,KAAK,OAAO;AAErB,UAAM,mBACJ,MAAM,2BAA2B,UAAU,gBAAgB;AAC7D,QAAI,CAAC,kBAAkB;AACrB,YAAM,IAAI,MAAM,sBAAsB,gBAAgB,aAAa;AAAA,IACrE;AAGA,UAAM,yBAAyB,MAC7B,8BACA,OAAO,KAAK,OAAO;AAErB,UAAM,SAAS,MAAM,uBAAuB;AAAA,MAC1C,KAAK;AAAA,MACL,UAAU;AAAA,MACV,iBAAiB;AAAA,IAAA;AAGnB,QAAI,CAAC,QAAQ;AAEX,YAAM,eAAe,MAAM,uBAAuB,OAAO;AAAA,QACvD,eAAe,KAAK;AAAA,QACpB,aAAa,UAAU;AAAA,QACvB,QAAQ,iBAAiB;AAAA,QACzB,kBAAkB,gBAAgB;AAAA,MAAA,CACnC;AACD,YAAM,aAAa,KAAA;AASnB,UAAI,iBAAiB,YAAY;AAC/B,cAAM,UACJ,wBAAwB,qBAAqB,gBAAgB;AAC/D,YAAI,SAAS;AACX,gBAAM,QAAQ,MAAM,WAAW,cAAc;AAAA,QAC/C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,iBAAiB,SAGY;AACjC,UAAM,EAAE,8BAAA,IAAkC,MAAM,OAC9C,6CACF,EAAA,KAAA,OAAA,EAAA,CAAA;AACA,UAAM,EAAE,kCAAA,IAAsC,MAAM,OAClD,iDACF;AAEA,UAAM,yBAAyB,MAC7B,8BACA,OAAO,KAAK,OAAO;AAErB,UAAM,YAAY,SAAS,aAAa;AAGxC,QAAI;AACJ,QAAI,SAAS,UAAU;AACrB,YAAM,6BAA6B,MACjC,kCACA,OAAO,KAAK,OAAO;AAErB,YAAM,mBAAmB,MAAM,2BAA2B;AAAA,QACxD,QAAQ;AAAA,MAAA;AAEV,eAAS,kBAAkB;AAAA,IAC7B;AAGA,QAAI,cAAc,QAAQ;AACxB,aAAO,MAAM,uBAAuB,eAAe,KAAK,IAAI,MAAM;AAAA,IACpE,WAAW,cAAc,MAAM;AAC7B,aAAO,MAAM,uBAAuB,aAAa,KAAK,IAAI,MAAM;AAAA,IAClE,OAAO;AACL,aAAO,MAAM,uBAAuB,cAAc,KAAK,IAAI,MAAM;AAAA,IACnE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,mBAAmB,kBAA+C;AACtE,UAAM,EAAE,mBAAAC,mBAAA,IAAsB,MAAM,QAAA,QAAA,EAAA,KAAA,MAAA,mBAAA;AAIpC,UAAM,gBAAgB,MAAM,KAAK,iBAAiB;AAAA,MAChD,UAAU;AAAA,MACV,WAAW;AAAA,IAAA,CACZ;AAED,UAAM,oBAAoB,MAAOA,mBAA0B;AAAA,MACzD,KAAK;AAAA,IAAA;AAGP,UAAM,kBAA6B,CAAA;AACnC,UAAM,8BAAc,IAAA;AAEpB,eAAW,gBAAgB,eAAe;AAGxC,YAAM,SAAS,OAAO,aAAa,aAAa;AAChD,YAAM,OAAO,OAAO,aAAa,WAAW;AAC5C,YAAM,SAAS,OAAO,KAAK,EAAE;AAC7B,YAAM,UAAU,WAAW,SAAS,OAAO;AAE3C,UAAI,CAAC,QAAQ,IAAI,OAAO,GAAG;AACzB,gBAAQ,IAAI,OAAO;AACnB,cAAM,UAAU,MAAM,kBAAkB,IAAI,EAAE,IAAI,SAAS;AAC3D,YAAI,SAAS;AACX,0BAAgB,KAAK,OAAO;AAAA,QAC9B;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,mBACJ,WACA,kBACe;AACf,UAAM,EAAE,kCAAA,IAAsC,MAAM,OAClD,iDACF;AACA,UAAM,EAAE,8BAAA,IAAkC,MAAM,OAC9C,6CACF,EAAA,KAAA,OAAA,EAAA,CAAA;AAGA,UAAM,6BAA6B,MACjC,kCACA,OAAO,KAAK,OAAO;AAErB,UAAM,mBACJ,MAAM,2BAA2B,UAAU,gBAAgB;AAC7D,QAAI,CAAC,kBAAkB;AACrB,YAAM,IAAI,MAAM,sBAAsB,gBAAgB,aAAa;AAAA,IACrE;AAGA,UAAM,yBAAyB,MAC7B,8BACA,OAAO,KAAK,OAAO;AAErB,UAAM,gBAAgB,MAAM,uBAAuB,KAAK;AAAA,MACtD,OAAO;AAAA,QACL,eAAe,KAAK;AAAA,QACpB,aAAa,UAAU;AAAA,QACvB,QAAQ,iBAAiB;AAAA,MAAA;AAAA,IAC3B,CACD;AAED,eAAW,gBAAgB,eAAe;AACxC,YAAM,aAAa,OAAA;AAAA,IACrB;AAGA,QAAI,iBAAiB,YAAY;AAC/B,YAAM,uBAAuB,MAAM,uBAAuB,KAAK;AAAA,QAC7D,OAAO;AAAA,UACL,eAAe,UAAU;AAAA,UACzB,aAAa,KAAK;AAAA,UAClB,QAAQ,iBAAiB;AAAA,QAAA;AAAA,MAC3B,CACD;AAED,iBAAW,gBAAgB,sBAAsB;AAC/C,cAAM,aAAa,OAAA;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,cAA+B;AAQnC,UAAM,KAAK,KAAK,QAAQ,MAAM,KAAK,QAAQ;AAE3C,UAAM,iBAAiB,MAAM;AAAA,MAC3B,8BAA8B;AAAA,MAC9B;AAAA,QACE;AAAA,QACA,UAAU,KAAK;AAAA,QACf,WAAW;AAAA,UACT,aAAa,KAAK,QAAQ;AAAA,UAC1B,oBAAoB,KAAK,eAAe;AAAA,QAAA;AAAA,MAC1C;AAAA,IACF;AAGF,UAAM,KAAK,MAAM,KAAK,YAAA;AACtB,UAAM,WAAW,MAAM,GAAG;AAAA,MACxB,eAAe;AAAA,MACf,qBAAqB,eAAe,EAAE;AAAA,IAAA;AAGxC,WAAO,SAAS,KAAA;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAAQ,UAAoC;AAChD,WAAO,MAAM,KAAK,GAAG,QAAQ;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,aAAa,eACX,gBACA,QACoB;AAEpB,WAAO,CAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAa,WAAW,WAAuC;AAE7D,WAAO,CAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,aAAa,YACX,YACA,mBACoB;AAEpB,WAAO,CAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAa,cAAc,QAAyC;AAElE,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,aAA6B;AACjC,UAAM,EAAE,iBAAA,IAAqB,MAAM,OACjC,gCACF;AACA,UAAM,aAAa,MAAO,iBAAyB,OAAO,KAAK,OAAO;AACtE,WAAO,MAAM,WAAW,cAAc,KAAK,EAAY;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,mBAAmC;AACvC,UAAM,EAAE,iBAAA,IAAqB,MAAM,OACjC,gCACF;AACA,UAAM,aAAa,MAAO,iBAAyB,OAAO,KAAK,OAAO;AACtE,WAAO,MAAM,WAAW,oBAAoB,KAAK,EAAY;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,eAAe,SAIqB;AACxC,UAAM,EAAE,OAAA,IAAW,MAAM,OAAO,sBAAU;AAC1C,WAAO,MAAM,OAAO,SAAS,MAAM;AAAA,MACjC,GAAG;AAAA,MACH,IAAI,KAAK,SAAS;AAAA,IAAA,CACnB;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,oBAAoC;AACxC,UAAM,EAAE,uBAAA,IAA2B,MAAM,OACvC,qBACF,EAAA,KAAA,OAAA,EAAA,CAAA;AACA,UAAM,aAAa,MAAO,uBAA+B;AAAA,MACvD,KAAK;AAAA,IAAA;AAEP,WAAO,MAAM,WAAW,cAAc,KAAK,EAAY;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,iBAAiB,UAKN;AACf,UAAM,EAAE,uBAAA,IAA2B,MAAM,OACvC,qBACF,EAAA,KAAA,OAAA,EAAA,CAAA;AACA,UAAM,aAAa,MAAO,uBAA+B;AAAA,MACvD,KAAK;AAAA,IAAA;AAEP,WAAO,MAAM,WAAW,cAAc,MAAM,QAAQ;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,qBAAqC;AACzC,UAAM,EAAE,wBAAA,IAA4B,MAAM,OACxC,uCACF,EAAA,KAAA,OAAA,EAAA,CAAA;AACA,UAAM,aAAa,MAAO,wBAAgC;AAAA,MACxD,KAAK;AAAA,IAAA;AAEP,WAAO,MAAM,WAAW,cAAc,KAAK,EAAY;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,kBAAkB,WAOP;AACf,UAAM,EAAE,wBAAA,IAA4B,MAAM,OACxC,uCACF,EAAA,KAAA,OAAA,EAAA,CAAA;AACA,UAAM,aAAa,MAAO,wBAAgC;AAAA,MACxD,KAAK;AAAA,IAAA;AAEP,WAAO,MAAM,WAAW,cAAc,MAAM,SAAS;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,aAAa,QAAgB,IAAoB;AACrD,UAAM,EAAE,mBAAA,IAAuB,MAAM,OACnC,kCACF,EAAA,KAAA,OAAA,EAAA,CAAA;AACA,UAAM,aAAa,MAAO,mBAA2B,OAAO,KAAK,OAAO;AACxE,WAAO,MAAM,WAAW,kBAAkB,KAAK,IAAc,KAAK;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,aAAa,SAOF;AACf,UAAM,EAAE,mBAAA,IAAuB,MAAM,OACnC,kCACF,EAAA,KAAA,OAAA,EAAA,CAAA;AACA,UAAM,aAAa,MAAO,mBAA2B,OAAO,KAAK,OAAO;AACxE,WAAO,MAAM,WAAW,OAAO;AAAA,MAC7B,SAAS;AAAA,MACT,GAAG;AAAA,IAAA,CACJ;AAAA,EACH;AACF;AA9sBE,gBAAA;AAAA,EADC,SAAS,EAAE,UAAU,KAAA,CAAM;AAAA,GADjB,QAEX,WAAA,YAAA,CAAA;AAIA,gBAAA;AAAA,EADC,WAAW,eAAe,EAAE,UAAU,MAAM;AAAA,GALlC,QAMX,WAAA,UAAA,CAAA;AAGA,gBAAA;AAAA,EADC,MAAM,EAAE,QAAQ,KAAA,CAAM;AAAA,GARZ,QASX,WAAA,SAAA,CAAA;AAGA,gBAAA;AAAA,EADC,MAAM,EAAE,UAAU,KAAA,CAAM;AAAA,GAXd,QAYX,WAAA,QAAA,CAAA;AAMA,gBAAA;AAAA,EADC,UAAU,iBAAiB;AAAA,GAjBjB,QAkBX,WAAA,YAAA,CAAA;AAQA,gBAAA;AAAA,EADC,UAAU,uBAAuB,EAAE,YAAY,iBAAiB;AAAA,GAzBtD,QA0BX,WAAA,qBAAA,CAAA;AAGA,gBAAA;AAAA,EADC,UAAU,uBAAuB,EAAE,YAAY,eAAe;AAAA,GA5BpD,QA6BX,WAAA,mBAAA,CAAA;AA7BW,UAAN,gBAAA;AAAA,EAPN,aAAa,EAAE,MAAM,YAAY;AAAA,EACjC,KAAK;AAAA,IACJ,eAAe;AAAA,IACf,KAAK,EAAE,SAAS,CAAC,QAAQ,OAAO,UAAU,UAAU,QAAQ,EAAA;AAAA,IAC5D,KAAK,EAAE,SAAS,CAAC,QAAQ,OAAO,UAAU,QAAQ,EAAA;AAAA,IAClD,KAAK;AAAA,EAAA,CACN;AAAA,GACY,OAAA;AC9BN,MAAM,0BAA0B,eAAwB;AAAA,EAC7D,OAAgB,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ7B,MAAM,YAAY,OAAwC;AACxD,UAAM,kBAAkB,MAAM,YAAA;AAC9B,UAAM,WAAW,MAAM,KAAK,KAAK;AAAA,MAC/B,OAAO,EAAE,OAAO,gBAAA;AAAA,MAChB,OAAO;AAAA,IAAA,CACR;AACD,WAAO,SAAS,SAAS,IAAI,SAAS,CAAC,IAAI;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,WAAW,UAAsC;AAErD,UAAM,cAAc,MAAM,KAAK,KAAK,CAAA,CAAE;AAEtC,UAAM,WAAsB,CAAA;AAC5B,eAAW,WAAW,aAAa;AACjC,YAAM,OAAO,MAAM,QAAQ,YAAA;AAC3B,UAAI,SAAS,UAAU;AACrB,iBAAS,KAAK,OAAO;AAAA,MACvB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,iBACJ,YAC2C;AAC3C,UAAM,6BAAa,IAAA;AAEnB,eAAW,aAAa,YAAY;AAClC,YAAM,UAAU,MAAM,KAAK,IAAI,EAAE,IAAI,WAAW;AAChD,UAAI,SAAS;AACX,cAAM,WAAW,MAAM,QAAQ,YAAA;AAC/B,eAAO,IAAI,WAAW,QAAQ;AAAA,MAChC;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,oBACJ,SACe;AACf,eAAW,UAAU,SAAS;AAC5B,YAAM,UAAU,MAAM,KAAK,IAAI,EAAE,IAAI,OAAO,WAAW;AACvD,UAAI,SAAS;AACX,cAAM,QAAQ,eAAe,OAAO,IAAI;AAAA,MAC1C;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,YACJ,WACA,kBACoB;AACpB,UAAM,UAAU,MAAM,KAAK,IAAI,EAAE,IAAI,WAAW;AAChD,QAAI,CAAC,QAAS,QAAO,CAAA;AAErB,WAAO,MAAM,QAAQ,mBAAmB,gBAAgB;AAAA,EAC1D;AAAA,EAEA,MAAM,UAAU,WAAmB,cAAyC;AAC1E,WAAO,6BAA6B,MAAM,WAAW,YAAY;AAAA,EACnE;AAAA,EAEA,MAAM,SACJ,WACA,OACA,eAAe,cACf,YAAY,GACG;AACf,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA,EAEA,MAAM,YACJ,WACA,SACA,cACe;AACf,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,uBACJ,WACA,UAAiC,IACH;AAC9B,UAAM,WAAW,QAAQ,YAAY;AACrC,UAAM,8BAAc,IAAA;AACpB,UAAM,8BAAc,IAAA;AACpB,UAAM,QAA8C;AAAA,MAClD,EAAE,IAAI,WAAW,OAAO,EAAA;AAAA,IAAE;AAG5B,WAAO,MAAM,SAAS,GAAG;AACvB,YAAM,UAAU,MAAM,MAAA;AACtB,UAAI,CAAC,SAAS;AACZ;AAAA,MACF;AAEA,UAAI,QAAQ,IAAI,QAAQ,EAAE,KAAK,QAAQ,QAAQ,UAAU;AACvD;AAAA,MACF;AAEA,cAAQ,IAAI,QAAQ,EAAE;AACtB,cAAQ,IAAI,QAAQ,IAAI,QAAQ,KAAK;AAErC,UAAI,QAAQ,QAAQ,UAAU;AAC5B,cAAM,UAAU,MAAM,KAAK,YAAY,QAAQ,EAAE;AACjD,mBAAW,WAAW,SAAS;AAC7B,cAAI,QAAQ,MAAM,CAAC,QAAQ,IAAI,QAAQ,EAAE,GAAG;AAC1C,kBAAM,KAAK,EAAE,IAAI,QAAQ,IAAI,OAAO,QAAQ,QAAQ,GAAG;AAAA,UACzD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,aAAaC,WAAsC;AACvD,WAAO,KAAK,KAAK,EAAE,OAAO,EAAE,UAAAA,UAAA,GAAY;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,aAAiC;AACrC,WAAO,YAAqB,IAAI;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,gBAAgBA,WAAsC;AAC1D,WAAO,iBAA0B,MAAMA,WAAU,yBAAyB;AAAA,EAC5E;AACF;;;;;"}
@@ -1,11 +1,11 @@
1
1
  import { ObjectRegistry, foreignKey, smrt, SmrtObject, SmrtCollection, field } from "@happyvertical/smrt-core";
2
- import { P as ProfileType, a as Profile } from "./ProfileCollection-DU6wUJTO.js";
2
+ import { P as ProfileType, a as Profile } from "./ProfileCollection-DQD1uJEc.js";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
4
  import { N as NostrIdentityCollection, g as generateNostrKeypair, e as encryptPrivkey, a as NostrIdentity, v as verifyAuthEvent } from "./NostrIdentityCollection-DadQBHWy.js";
5
5
  import { ApiKey } from "./ApiKey-B2LKEaP8.js";
6
6
  import "./ApiKeyCollection-B6Op817e.js";
7
7
  import "./AuditLogCollection-BYqCj0uE.js";
8
- import "./ProfileAssetCollection-D_tk1kKG.js";
8
+ import "./ProfileAssetCollection-ChX4kLjN.js";
9
9
  import "./ProfileMetadataCollection-DEhmljMY.js";
10
10
  import "./ProfileMetafieldCollection-DMKhSHXX.js";
11
11
  import "./ProfileRelationshipCollection-C0IM8UQR.js";
@@ -341,8 +341,23 @@ function createMagicLinkService(config) {
341
341
  let created = false;
342
342
  if (!nostrIdentity) {
343
343
  const { Person: Person2 } = await Promise.resolve().then(() => ProfileTypes);
344
+ const { ProfileTypeCollection: ProfileTypeCollection2 } = await Promise.resolve().then(() => ProfileTypeCollection$1);
345
+ const typeCollection = await ProfileTypeCollection2.create({ db });
346
+ let personType = await typeCollection.getBySlug("person");
347
+ if (!personType) {
348
+ const { ProfileType: ProfileType2 } = await import("./ProfileCollection-DQD1uJEc.js").then((n) => n.c);
349
+ personType = new ProfileType2({
350
+ db,
351
+ slug: "person",
352
+ name: "Person",
353
+ description: "Individual person profile"
354
+ });
355
+ await personType.initialize();
356
+ await personType.save();
357
+ }
344
358
  profile = new Person2({
345
359
  db,
360
+ typeId: personType.id,
346
361
  email: normalizedEmail,
347
362
  name: normalizedEmail.split("@")[0]
348
363
  // Default name from email
@@ -692,7 +707,7 @@ async function findProfileByExternalId(provider, externalId, options) {
692
707
  if (profile) return profile;
693
708
  }
694
709
  }
695
- const { ProfileCollection } = await import("./ProfileCollection-DU6wUJTO.js").then((n) => n.d);
710
+ const { ProfileCollection } = await import("./ProfileCollection-DQD1uJEc.js").then((n) => n.d);
696
711
  const profileCollection = await ProfileCollection.create(options);
697
712
  const profiles = await profileCollection.list({
698
713
  where: {
@@ -725,7 +740,7 @@ async function createProfileFromOidc(claims, provider, options) {
725
740
  }
726
741
  }
727
742
  if (claims.email && claims.email_verified) {
728
- const { ProfileCollection } = await import("./ProfileCollection-DU6wUJTO.js").then((n) => n.d);
743
+ const { ProfileCollection } = await import("./ProfileCollection-DQD1uJEc.js").then((n) => n.d);
729
744
  const profileCollection = await ProfileCollection.create(options);
730
745
  const existingProfile = await profileCollection.findByEmail(claims.email);
731
746
  if (existingProfile) {
@@ -751,7 +766,7 @@ async function createProfileFromOidc(claims, provider, options) {
751
766
  const typeCollection = await ProfileTypeCollection2.create(options);
752
767
  let personType = await typeCollection.getBySlug("person");
753
768
  if (!personType) {
754
- const { ProfileType: ProfileType2 } = await import("./ProfileCollection-DU6wUJTO.js").then((n) => n.c);
769
+ const { ProfileType: ProfileType2 } = await import("./ProfileCollection-DQD1uJEc.js").then((n) => n.c);
755
770
  personType = new ProfileType2({
756
771
  ...options,
757
772
  slug: "person",
@@ -806,7 +821,7 @@ async function createProfileFromNostr(email, nostrData, options) {
806
821
  const typeCollection = await ProfileTypeCollection2.create(options);
807
822
  let personType = await typeCollection.getBySlug("person");
808
823
  if (!personType) {
809
- const { ProfileType: ProfileType2 } = await import("./ProfileCollection-DU6wUJTO.js").then((n) => n.c);
824
+ const { ProfileType: ProfileType2 } = await import("./ProfileCollection-DQD1uJEc.js").then((n) => n.c);
810
825
  personType = new ProfileType2({
811
826
  ...options,
812
827
  slug: "person",
@@ -1011,4 +1026,4 @@ export {
1011
1026
  parseNip05Identifier as p,
1012
1027
  resolveIdentity as r
1013
1028
  };
1014
- //# sourceMappingURL=index-jFtOWsAV.js.map
1029
+ //# sourceMappingURL=index-DHLYp075.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-DHLYp075.js","sources":["../../src/__smrt-register__.ts","../../src/models/MagicLinkToken.ts","../../src/collections/MagicLinkTokenCollection.ts","../../src/auth/magicLinkService.ts","../../src/auth/nip05Handler.ts","../../src/models/OidcIdentity.ts","../../src/auth/resolveIdentity.ts","../../src/collections/OidcIdentityCollection.ts","../../src/collections/ProfileTypeCollection.ts","../../src/models/ProfileTypes.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// `new URL('./manifest.json', import.meta.url)` resolves at runtime to the\n// manifest sitting next to this module's compiled output. Vite warns at build\n// time that it cannot pre-resolve the URL; that is the intended behavior —\n// the URL must resolve to dist/manifest.json at runtime, not be inlined.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * MagicLinkToken - Temporary tokens for email verification\n *\n * Stores hashed magic link tokens with expiration. Tokens are single-use\n * and automatically invalidated after verification or expiration.\n */\n\nimport { createHash, randomBytes } from 'node:crypto';\nimport {\n foreignKey,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport type { NostrIdentity } from './NostrIdentity';\n\nexport interface MagicLinkTokenOptions extends SmrtObjectOptions {\n nostrIdentityId?: string;\n tokenHash?: string;\n email?: string;\n expiresAt?: Date;\n usedAt?: Date | null;\n requestedFromIp?: string;\n createdAt?: Date;\n}\n\nexport interface GenerateTokenResult {\n /** The plaintext token (only available at creation time) */\n token: string;\n /** The MagicLinkToken record */\n magicLinkToken: MagicLinkToken;\n}\n\n/** Default token expiration: 15 minutes */\nconst DEFAULT_EXPIRATION_MINUTES = 15;\n\n@smrt({\n tableName: 'magic_link_tokens',\n api: { exclude: ['*'] }, // No API access - internal only\n mcp: { exclude: ['*'] },\n cli: { include: ['list'] }, // Admin visibility only\n})\nexport class MagicLinkToken extends SmrtObject {\n /**\n * Link to the NostrIdentity this token is for\n */\n @foreignKey('NostrIdentity', { required: true })\n nostrIdentityId?: string;\n\n /**\n * SHA-256 hash of the token (never store plaintext)\n */\n tokenHash: string = '';\n\n /**\n * Email this token was sent to\n */\n email: string = '';\n\n /**\n * When this token expires\n */\n expiresAt: Date = new Date(\n Date.now() + DEFAULT_EXPIRATION_MINUTES * 60 * 1000,\n );\n\n /**\n * When this token was used (null = unused)\n */\n usedAt: Date | null = null;\n\n /**\n * IP address that requested the token (for rate limiting)\n */\n requestedFromIp: string = '';\n\n /**\n * When this token was created (for rate limiting)\n */\n createdAt: Date = new Date();\n\n constructor(options: MagicLinkTokenOptions = {}) {\n super(options);\n if (options.nostrIdentityId) this.nostrIdentityId = options.nostrIdentityId;\n if (options.tokenHash) this.tokenHash = options.tokenHash;\n if (options.email) this.email = options.email;\n if (options.expiresAt) this.expiresAt = options.expiresAt;\n if (options.usedAt !== undefined) this.usedAt = options.usedAt;\n if (options.requestedFromIp) this.requestedFromIp = options.requestedFromIp;\n if (options.createdAt) this.createdAt = options.createdAt;\n }\n\n /**\n * Hash a token using SHA-256\n */\n static hashToken(token: string): string {\n return createHash('sha256').update(token).digest('hex');\n }\n\n /**\n * Generate a new magic link token\n * @param nostrIdentity - The identity this token is for\n * @param email - The email address to send to\n * @param options - Additional options\n */\n static async generate(\n nostrIdentity: NostrIdentity,\n email: string,\n options: {\n expiresInMinutes?: number;\n requestedFromIp?: string;\n db?: SmrtObjectOptions['db'];\n } = {},\n ): Promise<GenerateTokenResult> {\n // Generate 32 bytes of random data (256 bits of entropy)\n const tokenBytes = randomBytes(32);\n const token = tokenBytes.toString('base64url');\n const tokenHash = MagicLinkToken.hashToken(token);\n\n const expiresInMinutes =\n options.expiresInMinutes ?? DEFAULT_EXPIRATION_MINUTES;\n const expiresAt = new Date(Date.now() + expiresInMinutes * 60 * 1000);\n\n const magicLinkToken = new MagicLinkToken({\n db: options.db ?? nostrIdentity.options?.db,\n nostrIdentityId: nostrIdentity.id as string,\n tokenHash,\n email: email.toLowerCase(),\n expiresAt,\n requestedFromIp: options.requestedFromIp || '',\n });\n\n await magicLinkToken.initialize();\n await magicLinkToken.save();\n\n return { token, magicLinkToken };\n }\n\n /**\n * Verify a token and return the MagicLinkToken if valid\n * @param token - The plaintext token to verify\n * @param options - Database options\n * @returns The MagicLinkToken if valid, null otherwise\n */\n static async verify(\n token: string,\n options: SmrtObjectOptions = {},\n ): Promise<MagicLinkToken | null> {\n const tokenHash = MagicLinkToken.hashToken(token);\n\n const { MagicLinkTokenCollection } = await import(\n '../collections/MagicLinkTokenCollection'\n );\n const collection = await (MagicLinkTokenCollection as any).create(options);\n\n const magicLinkToken = await collection.findOne({\n where: { tokenHash },\n });\n\n if (!magicLinkToken) {\n return null;\n }\n\n // Check if valid\n if (!magicLinkToken.isValid()) {\n return null;\n }\n\n return magicLinkToken;\n }\n\n /**\n * Check if this token is valid (not expired and not used)\n */\n isValid(): boolean {\n if (this.usedAt !== null) {\n return false;\n }\n if (new Date() > this.expiresAt) {\n return false;\n }\n return true;\n }\n\n /**\n * Check if this token has expired\n */\n isExpired(): boolean {\n return new Date() > this.expiresAt;\n }\n\n /**\n * Check if this token has been used\n */\n isUsed(): boolean {\n return this.usedAt !== null;\n }\n\n /**\n * Mark this token as used\n */\n async markUsed(): Promise<void> {\n this.usedAt = new Date();\n await this.save();\n }\n\n /**\n * Get the NostrIdentity this token is for\n */\n async getNostrIdentity(): Promise<NostrIdentity | null> {\n return (await this.getRelated('nostrIdentityId')) as NostrIdentity | null;\n }\n\n /**\n * Get time remaining until expiration in seconds\n */\n getTimeRemainingSeconds(): number {\n const now = new Date();\n const diff = this.expiresAt.getTime() - now.getTime();\n return Math.max(0, Math.floor(diff / 1000));\n }\n}\n","/**\n * MagicLinkTokenCollection - Collection for managing magic link tokens\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { MagicLinkToken } from '../models/MagicLinkToken';\n\nexport class MagicLinkTokenCollection extends SmrtCollection<MagicLinkToken> {\n static readonly _itemClass = MagicLinkToken;\n\n /**\n * Find token by hash\n */\n async findByTokenHash(tokenHash: string): Promise<MagicLinkToken | null> {\n return await this.findOne({\n where: { tokenHash },\n });\n }\n\n /**\n * Find active (non-expired, non-used) tokens for an email\n */\n async findActiveForEmail(email: string): Promise<MagicLinkToken[]> {\n const tokens = await this.list({\n where: { email: email.toLowerCase() },\n });\n return tokens.filter((token) => token.isValid());\n }\n\n /**\n * Find tokens for a NostrIdentity\n */\n async findByIdentity(nostrIdentityId: string): Promise<MagicLinkToken[]> {\n return await this.list({\n where: { nostrIdentityId },\n });\n }\n\n /**\n * Count tokens created for an email within a time window (for rate limiting)\n * @param email - Email address\n * @param withinMinutes - Time window in minutes\n */\n async countRecentByEmail(\n email: string,\n withinMinutes: number,\n ): Promise<number> {\n const cutoff = new Date(Date.now() - withinMinutes * 60 * 1000);\n const tokens = await this.list({\n where: { email: email.toLowerCase() },\n });\n\n // Filter tokens created after cutoff\n // Note: We use createdAt from SmrtObject base\n return tokens.filter((token) => {\n const createdAt = (token as any).createdAt;\n return createdAt && new Date(createdAt) > cutoff;\n }).length;\n }\n\n /**\n * Count tokens created from an IP within a time window (for rate limiting)\n * @param ip - IP address\n * @param withinMinutes - Time window in minutes\n */\n async countRecentByIp(ip: string, withinMinutes: number): Promise<number> {\n const cutoff = new Date(Date.now() - withinMinutes * 60 * 1000);\n const tokens = await this.list({\n where: { requestedFromIp: ip },\n });\n\n // Filter tokens created after cutoff\n return tokens.filter((token) => {\n const createdAt = (token as any).createdAt;\n return createdAt && new Date(createdAt) > cutoff;\n }).length;\n }\n\n /**\n * Verify a token and return it if valid\n */\n async verify(token: string): Promise<MagicLinkToken | null> {\n const tokenHash = MagicLinkToken.hashToken(token);\n const magicLinkToken = await this.findByTokenHash(tokenHash);\n\n if (!magicLinkToken) {\n return null;\n }\n\n if (!magicLinkToken.isValid()) {\n return null;\n }\n\n return magicLinkToken;\n }\n\n /**\n * Clean up expired tokens by deleting them\n * @returns Number of tokens deleted\n */\n async cleanupExpired(): Promise<number> {\n const now = new Date();\n const allTokens = await this.list({});\n let deleted = 0;\n\n for (const token of allTokens) {\n if (token.isExpired() || token.isUsed()) {\n await token.delete();\n deleted++;\n }\n }\n\n return deleted;\n }\n\n /**\n * Revoke all tokens for a NostrIdentity (e.g., when identity is deleted)\n */\n async revokeForIdentity(nostrIdentityId: string): Promise<number> {\n const tokens = await this.findByIdentity(nostrIdentityId);\n let revoked = 0;\n\n for (const token of tokens) {\n if (!token.isUsed()) {\n await token.markUsed(); // Mark as used to invalidate\n revoked++;\n }\n }\n\n return revoked;\n }\n\n /**\n * Check if rate limit is exceeded for email\n * @param email - Email address\n * @param maxPerHour - Maximum tokens allowed per hour (default: 5)\n */\n async isRateLimitedByEmail(\n email: string,\n maxPerHour: number = 5,\n ): Promise<boolean> {\n const count = await this.countRecentByEmail(email, 60);\n return count >= maxPerHour;\n }\n\n /**\n * Check if rate limit is exceeded for IP\n * @param ip - IP address\n * @param maxPerHour - Maximum tokens allowed per hour (default: 10)\n */\n async isRateLimitedByIp(\n ip: string,\n maxPerHour: number = 10,\n ): Promise<boolean> {\n const count = await this.countRecentByIp(ip, 60);\n return count >= maxPerHour;\n }\n}\n","/**\n * Magic Link Service\n *\n * Handles the magic link authentication flow including:\n * - Token generation and storage\n * - Email sending (via callback)\n * - Token verification\n * - Rate limiting checks\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { MagicLinkTokenCollection } from '../collections/MagicLinkTokenCollection';\nimport { NostrIdentityCollection } from '../collections/NostrIdentityCollection';\nimport { MagicLinkToken } from '../models/MagicLinkToken';\nimport type { NostrIdentity } from '../models/NostrIdentity';\nimport type { Profile } from '../models/Profile';\nimport { encryptPrivkey, generateNostrKeypair } from './nostrCrypto';\n\nexport interface MagicLinkConfig {\n /** Base URL for magic links (e.g., \"https://example.com\") */\n baseUrl: string;\n /** Path for verification endpoint (default: \"/auth/verify\") */\n verifyPath?: string;\n /** Token expiration in minutes (default: 15) */\n expiresInMinutes?: number;\n /** Maximum tokens per email per hour (default: 5) */\n maxTokensPerEmailPerHour?: number;\n /** Maximum tokens per IP per hour (default: 10) */\n maxTokensPerIpPerHour?: number;\n /** Server master secret for encryption */\n masterSecret: string;\n /** Callback to send the email */\n sendEmail: (to: string, magicLink: string) => Promise<void>;\n /** Database options */\n db: SmrtObjectOptions['db'];\n}\n\nexport interface InitiateResult {\n success: boolean;\n /** The Nostr identity (existing or new) */\n nostrIdentity?: NostrIdentity;\n /** The profile (existing or new) */\n profile?: Profile;\n /** True if a new profile was created */\n created: boolean;\n /** Error message if success is false */\n error?: string;\n /** Error code for programmatic handling */\n errorCode?: 'RATE_LIMITED_EMAIL' | 'RATE_LIMITED_IP' | 'EMAIL_SEND_FAILED';\n}\n\nexport interface VerifyResult {\n success: boolean;\n /** The profile */\n profile?: Profile;\n /** The Nostr identity */\n nostrIdentity?: NostrIdentity;\n /** The decrypted keypair (only on success) */\n keypair?: {\n pubkey: string;\n privkey: string;\n npub: string;\n nsec: string;\n };\n /** Error message if success is false */\n error?: string;\n /** Error code for programmatic handling */\n errorCode?: 'INVALID_TOKEN' | 'EXPIRED_TOKEN' | 'USED_TOKEN' | 'NOT_FOUND';\n}\n\nexport interface MagicLinkService {\n /**\n * Initiate magic link flow - generates token and sends email\n */\n initiate(\n email: string,\n options?: {\n requestedFromIp?: string;\n nip05Username?: string;\n },\n ): Promise<InitiateResult>;\n\n /**\n * Verify a magic link token and return decrypted keypair\n */\n verify(token: string): Promise<VerifyResult>;\n}\n\n/**\n * Create a magic link service instance\n */\nexport function createMagicLinkService(\n config: MagicLinkConfig,\n): MagicLinkService {\n const {\n baseUrl,\n verifyPath = '/auth/verify',\n expiresInMinutes = 15,\n maxTokensPerEmailPerHour = 5,\n maxTokensPerIpPerHour = 10,\n masterSecret,\n sendEmail,\n db,\n } = config;\n\n return {\n async initiate(\n email: string,\n options: {\n requestedFromIp?: string;\n nip05Username?: string;\n } = {},\n ): Promise<InitiateResult> {\n const normalizedEmail = email.toLowerCase().trim();\n const { requestedFromIp, nip05Username } = options;\n\n // Create collections\n const identityCollection = await NostrIdentityCollection.create({ db });\n const tokenCollection = await MagicLinkTokenCollection.create({ db });\n\n // Rate limiting checks\n if (\n await tokenCollection.isRateLimitedByEmail(\n normalizedEmail,\n maxTokensPerEmailPerHour,\n )\n ) {\n return {\n success: false,\n created: false,\n error: 'Too many requests for this email. Please try again later.',\n errorCode: 'RATE_LIMITED_EMAIL',\n };\n }\n\n if (\n requestedFromIp &&\n (await tokenCollection.isRateLimitedByIp(\n requestedFromIp,\n maxTokensPerIpPerHour,\n ))\n ) {\n return {\n success: false,\n created: false,\n error: 'Too many requests from this IP. Please try again later.',\n errorCode: 'RATE_LIMITED_IP',\n };\n }\n\n // Check if identity exists\n let nostrIdentity = await identityCollection.findByEmail(normalizedEmail);\n let profile: Profile | null = null;\n let created = false;\n\n if (!nostrIdentity) {\n // Create new identity and profile\n const { Person } = await import('../models/ProfileTypes');\n const { ProfileTypeCollection } = await import(\n '../collections/ProfileTypeCollection'\n );\n\n // Profile.typeId is a required FK, so resolve (or seed) the canonical\n // 'person' type before creating the profile — otherwise `.save()`\n // fails required-field validation. Mirrors createProfileFromNostr /\n // createProfileFromOidc in resolveIdentity.ts.\n const typeCollection = await ProfileTypeCollection.create({ db });\n let personType = await typeCollection.getBySlug('person');\n if (!personType) {\n const { ProfileType } = await import('../models/ProfileType');\n personType = new ProfileType({\n db,\n slug: 'person',\n name: 'Person',\n description: 'Individual person profile',\n });\n await personType.initialize();\n await personType.save();\n }\n\n // Create a new profile for this user (Person is an STI subclass of Profile)\n profile = new Person({\n db,\n typeId: personType.id as string,\n email: normalizedEmail,\n name: normalizedEmail.split('@')[0], // Default name from email\n });\n await profile.initialize();\n await profile.save();\n\n // Generate keypair and encrypt\n const keypair = generateNostrKeypair();\n const encrypted = encryptPrivkey(keypair.privkey, masterSecret);\n\n // Create Nostr identity\n const { NostrIdentity: NostrIdentityClass } = await import(\n '../models/NostrIdentity'\n );\n nostrIdentity = new NostrIdentityClass({\n db,\n profileId: profile.id!,\n pubkey: keypair.pubkey,\n encryptedPrivkey: encrypted.ciphertext,\n encryptionIv: encrypted.iv,\n encryptionTag: encrypted.tag,\n email: normalizedEmail,\n nip05Username:\n nip05Username?.toLowerCase() || normalizedEmail.split('@')[0],\n });\n await nostrIdentity.initialize();\n await nostrIdentity.save();\n\n created = true;\n } else {\n profile = await nostrIdentity.getProfile();\n }\n\n // Generate magic link token\n const { token } = await MagicLinkToken.generate(\n nostrIdentity,\n normalizedEmail,\n {\n expiresInMinutes,\n requestedFromIp,\n db,\n },\n );\n\n // Build magic link URL\n const magicLink = `${baseUrl}${verifyPath}?token=${encodeURIComponent(token)}`;\n\n // Send email\n try {\n await sendEmail(normalizedEmail, magicLink);\n } catch (emailError) {\n return {\n success: false,\n created,\n nostrIdentity,\n profile: profile || undefined,\n error: 'Failed to send email. Please try again.',\n errorCode: 'EMAIL_SEND_FAILED',\n };\n }\n\n return {\n success: true,\n created,\n nostrIdentity,\n profile: profile || undefined,\n };\n },\n\n async verify(token: string): Promise<VerifyResult> {\n // Verify token\n const magicLinkToken = await MagicLinkToken.verify(token, { db });\n\n if (!magicLinkToken) {\n // Try to get more specific error\n const tokenHash = MagicLinkToken.hashToken(token);\n const tokenCollection = await MagicLinkTokenCollection.create({ db });\n const existingToken = await tokenCollection.findByTokenHash(tokenHash);\n\n if (!existingToken) {\n return {\n success: false,\n error: 'Invalid or expired token.',\n errorCode: 'NOT_FOUND',\n };\n }\n\n if (existingToken.isUsed()) {\n return {\n success: false,\n error: 'This link has already been used.',\n errorCode: 'USED_TOKEN',\n };\n }\n\n if (existingToken.isExpired()) {\n return {\n success: false,\n error: 'This link has expired. Please request a new one.',\n errorCode: 'EXPIRED_TOKEN',\n };\n }\n\n return {\n success: false,\n error: 'Invalid token.',\n errorCode: 'INVALID_TOKEN',\n };\n }\n\n // Mark token as used\n await magicLinkToken.markUsed();\n\n // Get the Nostr identity\n const nostrIdentity = await magicLinkToken.getNostrIdentity();\n if (!nostrIdentity) {\n return {\n success: false,\n error: 'Identity not found.',\n errorCode: 'NOT_FOUND',\n };\n }\n\n // Mark identity as verified\n await nostrIdentity.markVerified();\n\n // Get profile\n const profile = await nostrIdentity.getProfile();\n\n // Decrypt keypair\n const keypair = nostrIdentity.getKeypair(masterSecret);\n\n return {\n success: true,\n profile: profile || undefined,\n nostrIdentity,\n keypair,\n };\n },\n };\n}\n","/**\n * NIP-05 Handler\n *\n * Provides a helper for serving the /.well-known/nostr.json endpoint\n * for NIP-05 identity verification.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/05.md\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport {\n type Nip05Response,\n NostrIdentityCollection,\n} from '../collections/NostrIdentityCollection';\n\nexport interface Nip05HandlerConfig {\n /** Database options */\n db: SmrtObjectOptions['db'];\n /** Optional: Additional relays to include for all users */\n defaultRelays?: string[];\n /** Optional: Cache TTL in seconds (default: 300 = 5 minutes) */\n cacheTtlSeconds?: number;\n}\n\nexport interface Nip05Request {\n /** The 'name' query parameter from the request */\n name?: string | null;\n}\n\nexport interface Nip05HandlerResult {\n /** The NIP-05 response body */\n body: Nip05Response;\n /** Suggested headers for the response */\n headers: Record<string, string>;\n /** HTTP status code */\n status: number;\n}\n\n/**\n * Create a NIP-05 handler\n *\n * @example\n * // SvelteKit\n * import { createNip05Handler } from '@happyvertical/smrt-profiles';\n *\n * const handler = createNip05Handler({ db: { type: 'postgres', url: DATABASE_URL } });\n *\n * export async function GET({ url }) {\n * const result = await handler({ name: url.searchParams.get('name') });\n * return new Response(JSON.stringify(result.body), {\n * status: result.status,\n * headers: result.headers,\n * });\n * }\n *\n * @example\n * // Express\n * import { createNip05Handler } from '@happyvertical/smrt-profiles';\n *\n * const handler = createNip05Handler({ db: { type: 'postgres', url: DATABASE_URL } });\n *\n * app.get('/.well-known/nostr.json', async (req, res) => {\n * const result = await handler({ name: req.query.name });\n * res.status(result.status).set(result.headers).json(result.body);\n * });\n */\nexport function createNip05Handler(config: Nip05HandlerConfig) {\n const { db, defaultRelays = [], cacheTtlSeconds = 300 } = config;\n\n return async function handleNip05Request(\n request: Nip05Request,\n ): Promise<Nip05HandlerResult> {\n const { name } = request;\n\n const collection = await NostrIdentityCollection.create({ db });\n const response = await collection.getNip05Response(name || undefined);\n\n // Add default relays if configured\n if (defaultRelays.length > 0 && Object.keys(response.names).length > 0) {\n response.relays = {};\n for (const pubkey of Object.values(response.names)) {\n response.relays[pubkey] = defaultRelays;\n }\n }\n\n // If specific name requested but not found, return empty names\n if (name && Object.keys(response.names).length === 0) {\n return {\n body: { names: {} },\n headers: {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n 'Cache-Control': `public, max-age=${cacheTtlSeconds}`,\n },\n status: 200, // NIP-05 spec says return 200 with empty names, not 404\n };\n }\n\n return {\n body: response,\n headers: {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n 'Cache-Control': `public, max-age=${cacheTtlSeconds}`,\n },\n status: 200,\n };\n };\n}\n\n/**\n * Validate a NIP-05 identifier format\n * @param identifier - The identifier to validate (e.g., \"alice@example.com\")\n */\nexport function isValidNip05Identifier(identifier: string): boolean {\n // NIP-05 format: <local-part>@<domain>\n const parts = identifier.split('@');\n if (parts.length !== 2) {\n return false;\n }\n\n const [localPart, domain] = parts;\n\n // Local part: alphanumeric, _, -\n if (!/^[a-z0-9_-]+$/i.test(localPart)) {\n return false;\n }\n\n // Domain: basic domain validation\n if (!/^[a-z0-9.-]+\\.[a-z]{2,}$/i.test(domain)) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Parse a NIP-05 identifier into its parts\n * @param identifier - The identifier to parse (e.g., \"alice@example.com\")\n */\nexport function parseNip05Identifier(identifier: string): {\n localPart: string;\n domain: string;\n} | null {\n if (!isValidNip05Identifier(identifier)) {\n return null;\n }\n\n const [localPart, domain] = identifier.split('@');\n return { localPart, domain };\n}\n","/**\n * OidcIdentity - Links OIDC provider identities to Profile\n *\n * Stores the mapping between external OIDC providers (Keycloak, Google, GitHub)\n * and internal Profile records. Multiple identities can link to a single profile.\n */\n\nimport {\n field,\n foreignKey,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport type { Profile } from './Profile';\n\nexport interface OidcIdentityOptions extends SmrtObjectOptions {\n profileId?: string;\n provider?: string;\n issuer?: string;\n subject?: string;\n email?: string;\n lastUsedAt?: Date | null;\n}\n\n@smrt({\n tableName: 'oidc_identities',\n api: { exclude: ['delete'] },\n mcp: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n})\nexport class OidcIdentity extends SmrtObject {\n /**\n * Link to the Profile (Person, Organization, Bot)\n */\n @foreignKey('Profile', { required: true })\n profileId?: string;\n\n /**\n * Provider name (e.g., 'keycloak', 'google', 'github')\n */\n @field({ type: 'text' })\n provider: string = '';\n\n /**\n * OIDC issuer URL (e.g., https://keycloak.example.com/realms/bmp)\n */\n @field({ type: 'text' })\n issuer: string = '';\n\n /**\n * OIDC subject claim - unique identifier from the provider\n */\n @field({ type: 'text' })\n subject: string = '';\n\n /**\n * Cached email from the IdP (for display/lookup)\n */\n @field({ type: 'text' })\n email: string = '';\n\n /**\n * Last time this identity was used for authentication\n */\n @field({ type: 'datetime', nullable: true })\n lastUsedAt: Date | null = null;\n\n constructor(options: OidcIdentityOptions = {}) {\n super(options);\n if (options.profileId) this.profileId = options.profileId;\n if (options.provider) this.provider = options.provider;\n if (options.issuer) this.issuer = options.issuer;\n if (options.subject) this.subject = options.subject;\n if (options.email) this.email = options.email;\n if (options.lastUsedAt !== undefined) this.lastUsedAt = options.lastUsedAt;\n }\n\n /**\n * Get the linked Profile\n */\n async getProfile(): Promise<Profile | null> {\n return (await this.getRelated('profileId')) as Profile | null;\n }\n\n /**\n * Find identity by issuer and subject\n */\n static async findBySubject(\n issuer: string,\n subject: string,\n options: SmrtObjectOptions = {},\n ): Promise<OidcIdentity | null> {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const collection = await (OidcIdentityCollection as any).create(options);\n return await collection.findOne({\n where: { issuer, subject },\n });\n }\n\n /**\n * Find or create identity for a profile\n */\n static async findOrCreate(\n profile: Profile,\n oidcData: {\n provider: string;\n issuer: string;\n subject: string;\n email?: string;\n },\n options: SmrtObjectOptions = {},\n ): Promise<OidcIdentity> {\n const existing = await OidcIdentity.findBySubject(\n oidcData.issuer,\n oidcData.subject,\n options,\n );\n\n if (existing) {\n // Update last used\n existing.lastUsedAt = new Date();\n if (oidcData.email) existing.email = oidcData.email;\n await existing.save();\n return existing;\n }\n\n // Create new\n const identity = new OidcIdentity({\n ...options,\n profileId: profile.id as string,\n provider: oidcData.provider,\n issuer: oidcData.issuer,\n subject: oidcData.subject,\n email: oidcData.email || '',\n });\n await identity.initialize();\n await identity.save();\n return identity;\n }\n\n /**\n * Record usage of this identity\n */\n async recordUsage(): Promise<void> {\n this.lastUsedAt = new Date();\n await this.save();\n }\n}\n","/**\n * resolveIdentity - Resolves authentication context to a Profile\n *\n * Used by apps (blindmanpress.com) to resolve incoming auth to a Profile.\n * Modules (aedile, praeco) should receive the resolved profile from the app.\n *\n * Resolution order:\n * 1. API key header → ApiKey → Profile\n * 2. OIDC session → OidcIdentity → Profile\n * 3. Nostr auth → NostrIdentity → Profile\n * 4. Actor header (CI pass-through) → Profile lookup\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { ApiKey } from '../models/ApiKey';\nimport { NostrIdentity } from '../models/NostrIdentity';\nimport { OidcIdentity } from '../models/OidcIdentity';\nimport type { Profile } from '../models/Profile';\nimport { type NostrEvent, verifyAuthEvent } from './nostrCrypto';\n\n/**\n * Context provided to resolveIdentity\n */\nexport interface AuthContext {\n /**\n * API key from X-API-Key header or similar\n */\n apiKey?: string | null;\n\n /**\n * OIDC session data (from @auth/sveltekit or similar)\n */\n oidcSession?: {\n sub?: string;\n iss?: string;\n email?: string;\n name?: string;\n } | null;\n\n /**\n * Actor identifier for CI pass-through identity\n * Usually the GitHub actor (username) who triggered the workflow\n */\n actor?: string | null;\n\n /**\n * Nostr authentication data (NIP-42 style)\n */\n nostrAuth?: {\n /** Signed Nostr event for authentication */\n event: NostrEvent;\n /** Expected challenge (to prevent replay attacks) */\n challenge: string;\n } | null;\n\n /**\n * Database/persistence options\n */\n db?: SmrtObjectOptions['db'];\n}\n\n/**\n * Result of identity resolution\n */\nexport interface ResolveIdentityResult {\n /**\n * The resolved profile, or null if not authenticated\n */\n profile: Profile | null;\n\n /**\n * How the identity was resolved\n */\n source: 'api_key' | 'oidc' | 'nostr' | 'actor' | 'none';\n\n /**\n * The API key record if authenticated via API key\n */\n apiKey?: ApiKey;\n\n /**\n * The OIDC identity record if authenticated via OIDC\n */\n oidcIdentity?: OidcIdentity;\n\n /**\n * The Nostr identity record if authenticated via Nostr\n */\n nostrIdentity?: NostrIdentity;\n}\n\n/**\n * Resolve authentication context to a Profile\n *\n * @param context - The authentication context from the request\n * @returns The resolved profile and metadata\n *\n * @example\n * ```typescript\n * // In SvelteKit hooks.server.ts\n * import { resolveIdentity } from '@happyvertical/smrt-profiles';\n *\n * const identityMiddleware: Handle = async ({ event, resolve }) => {\n * const session = await event.locals.auth();\n *\n * const { profile, source } = await resolveIdentity({\n * oidcSession: session,\n * apiKey: event.request.headers.get('X-API-Key'),\n * actor: event.request.headers.get('X-Actor'),\n * db: { type: 'postgres', url: DATABASE_URL },\n * });\n *\n * event.locals.profile = profile;\n * event.locals.authSource = source;\n *\n * return resolve(event);\n * };\n * ```\n */\nexport async function resolveIdentity(\n context: AuthContext,\n): Promise<ResolveIdentityResult> {\n const options: SmrtObjectOptions = { db: context.db };\n\n // 1. Check API key header first (highest priority for programmatic access)\n if (context.apiKey) {\n const apiKey = await ApiKey.verify(context.apiKey, options);\n if (apiKey) {\n const profile = await apiKey.getProfile();\n if (profile) {\n return {\n profile,\n source: 'api_key',\n apiKey,\n };\n }\n }\n }\n\n // 2. Check OIDC session (web users)\n if (context.oidcSession?.sub && context.oidcSession?.iss) {\n const oidcIdentity = await OidcIdentity.findBySubject(\n context.oidcSession.iss,\n context.oidcSession.sub,\n options,\n );\n\n if (oidcIdentity) {\n // Record usage\n await oidcIdentity.recordUsage();\n\n const profile = await oidcIdentity.getProfile();\n if (profile) {\n return {\n profile,\n source: 'oidc',\n oidcIdentity,\n };\n }\n }\n }\n\n // 3. Check Nostr auth (NIP-42 style signed event)\n if (context.nostrAuth?.event && context.nostrAuth?.challenge) {\n const { event, challenge } = context.nostrAuth;\n\n // Verify the auth event\n const verifyResult = verifyAuthEvent(event, challenge);\n if (verifyResult.valid) {\n // Look up identity by public key\n const nostrIdentity = await NostrIdentity.findByPubkey(\n event.pubkey,\n options,\n );\n\n if (nostrIdentity) {\n // Record usage\n await nostrIdentity.recordUsage();\n\n const profile = await nostrIdentity.getProfile();\n if (profile) {\n return {\n profile,\n source: 'nostr',\n nostrIdentity,\n };\n }\n }\n }\n }\n\n // 4. Check actor for CI pass-through (look up by metadata)\n if (context.actor) {\n const profile = await findProfileByExternalId(\n 'github',\n context.actor,\n options,\n );\n if (profile) {\n return {\n profile,\n source: 'actor',\n };\n }\n }\n\n // No authentication found\n return {\n profile: null,\n source: 'none',\n };\n}\n\n/**\n * Find a profile by external ID (e.g., GitHub username)\n *\n * This looks up profiles by their linked external identities or metadata.\n *\n * @param provider - The external provider (e.g., 'github')\n * @param externalId - The external identifier (e.g., GitHub username)\n * @param options - Database options\n * @returns The profile or null\n */\nasync function findProfileByExternalId(\n provider: string,\n externalId: string,\n options: SmrtObjectOptions,\n): Promise<Profile | null> {\n // First check OIDC identities (if they use the provider)\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n\n const oidcCollection = await (OidcIdentityCollection as any).create(options);\n const identities = await oidcCollection.findByProvider(provider);\n\n for (const identity of identities) {\n // Check if the subject matches the external ID\n if (\n identity.subject === externalId ||\n identity.email?.includes(externalId)\n ) {\n const profile = await identity.getProfile();\n if (profile) return profile;\n }\n }\n\n // Could also check profile metadata for external IDs\n // This would require a standardized metadata field like 'github_username'\n const { ProfileCollection } = await import(\n '../collections/ProfileCollection'\n );\n const profileCollection = await (ProfileCollection as any).create(options);\n\n // Try to find by email that looks like a GitHub noreply\n const profiles = await profileCollection.list({\n where: {\n email: `${externalId}@users.noreply.github.com`,\n },\n limit: 1,\n });\n\n if (profiles.length > 0) {\n return profiles[0];\n }\n\n return null;\n}\n\n/**\n * Create a profile from OIDC claims if it doesn't exist\n *\n * Supports email-based account linking: if a user signs in with Google\n * and later with GitHub using the same email, they get the same profile.\n *\n * Resolution order:\n * 1. If OIDC identity (iss + sub) already exists → return linked profile\n * 2. If verified email provided, check if profile with same email exists → link new identity\n * 3. Otherwise, create new profile + identity\n *\n * Security considerations:\n * - Email-based linking only occurs when `email_verified` is true. This prevents\n * attackers from claiming unverified emails to hijack accounts.\n * - Linking is automatic and irreversible through this API. Multiple OIDC\n * identities from different providers sharing the same verified email will\n * be associated to the same Profile.\n * - If an OIDC provider does not supply an email, or the email changes later,\n * existing links are not automatically updated. New sign-ins without an email\n * or with a different email may result in a new Profile being created.\n * - This function trusts the OIDC provider to assert correct email_verified status.\n * Only use with trusted providers.\n *\n * @param claims - OIDC token claims\n * @param provider - Provider name (e.g., 'keycloak', 'google', 'github')\n * @param options - Database options\n * @returns The created or existing profile with linked OIDC identity\n */\nexport async function createProfileFromOidc(\n claims: {\n sub: string;\n iss: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n preferred_username?: string;\n },\n provider: string,\n options: SmrtObjectOptions,\n): Promise<{ profile: Profile; oidcIdentity: OidcIdentity; created: boolean }> {\n // 1. If OIDC identity already exists for this issuer+subject, return linked profile\n const existingIdentity = await OidcIdentity.findBySubject(\n claims.iss,\n claims.sub,\n options,\n );\n\n if (existingIdentity) {\n const profile = await existingIdentity.getProfile();\n if (profile) {\n // Update identity with latest claims\n if (claims.email) existingIdentity.email = claims.email;\n existingIdentity.lastUsedAt = new Date();\n await existingIdentity.save();\n\n return {\n profile,\n oidcIdentity: existingIdentity,\n created: false,\n };\n }\n }\n\n // 2. Email-based account linking: only link if email is verified (security)\n if (claims.email && claims.email_verified) {\n const { ProfileCollection } = await import(\n '../collections/ProfileCollection'\n );\n\n const profileCollection = await (ProfileCollection as any).create(options);\n const existingProfile = await profileCollection.findByEmail(claims.email);\n\n if (existingProfile) {\n // Link new OIDC identity to existing profile (email-based linking)\n const oidcIdentity = await OidcIdentity.findOrCreate(\n existingProfile,\n {\n provider,\n issuer: claims.iss,\n subject: claims.sub,\n email: claims.email,\n },\n options,\n );\n\n return {\n profile: existingProfile,\n oidcIdentity,\n created: false,\n };\n }\n }\n\n // 3. Create new profile\n const { Person } = await import('../models/ProfileTypes');\n const { ProfileTypeCollection } = await import(\n '../collections/ProfileTypeCollection'\n );\n\n // Get or create the 'person' type\n const typeCollection = await (ProfileTypeCollection as any).create(options);\n let personType = await typeCollection.getBySlug('person');\n\n if (!personType) {\n // Create the person type if it doesn't exist\n const { ProfileType } = await import('../models/ProfileType');\n personType = new ProfileType({\n ...options,\n slug: 'person',\n name: 'Person',\n description: 'Individual person profile',\n });\n await personType.initialize();\n await personType.save();\n }\n\n const profile = new Person({\n ...options,\n typeId: personType.id as string,\n email: claims.email || '',\n name: claims.name || claims.preferred_username || claims.sub,\n });\n await profile.initialize();\n await profile.save();\n\n // Link OIDC identity\n const oidcIdentity = await OidcIdentity.findOrCreate(\n profile,\n {\n provider,\n issuer: claims.iss,\n subject: claims.sub,\n email: claims.email,\n },\n options,\n );\n\n return {\n profile,\n oidcIdentity,\n created: true,\n };\n}\n\n/**\n * Create a profile from Nostr identity (used by magic link service)\n *\n * This is typically called internally by the magic link service,\n * but can be used directly if needed.\n *\n * @param email - Email address for the profile\n * @param nostrData - Encrypted Nostr keypair data\n * @param options - Database options\n * @returns The created profile with linked Nostr identity\n */\nexport async function createProfileFromNostr(\n email: string,\n nostrData: {\n pubkey: string;\n encryptedPrivkey: string;\n encryptionIv: string;\n encryptionTag: string;\n nip05Username?: string;\n },\n options: SmrtObjectOptions,\n): Promise<{\n profile: Profile;\n nostrIdentity: NostrIdentity;\n created: boolean;\n}> {\n const normalizedEmail = email.toLowerCase();\n\n // Check if Nostr identity already exists\n const existingIdentity = await NostrIdentity.findByEmail(\n normalizedEmail,\n options,\n );\n\n if (existingIdentity) {\n const profile = await existingIdentity.getProfile();\n if (profile) {\n return {\n profile,\n nostrIdentity: existingIdentity,\n created: false,\n };\n }\n }\n\n // Create new profile\n const { Person } = await import('../models/ProfileTypes');\n const { ProfileTypeCollection } = await import(\n '../collections/ProfileTypeCollection'\n );\n\n // Get or create the 'person' type\n const typeCollection = await (ProfileTypeCollection as any).create(options);\n let personType = await typeCollection.getBySlug('person');\n\n if (!personType) {\n const { ProfileType } = await import('../models/ProfileType');\n personType = new ProfileType({\n ...options,\n slug: 'person',\n name: 'Person',\n description: 'Individual person profile',\n });\n await personType.initialize();\n await personType.save();\n }\n\n const profile = new Person({\n ...options,\n typeId: personType.id as string,\n email: normalizedEmail,\n name: normalizedEmail.split('@')[0], // Default name from email\n });\n await profile.initialize();\n await profile.save();\n\n // Create Nostr identity\n const nostrIdentity = new NostrIdentity({\n ...options,\n profileId: profile.id as string,\n pubkey: nostrData.pubkey,\n encryptedPrivkey: nostrData.encryptedPrivkey,\n encryptionIv: nostrData.encryptionIv,\n encryptionTag: nostrData.encryptionTag,\n email: normalizedEmail,\n nip05Username:\n nostrData.nip05Username?.toLowerCase() || normalizedEmail.split('@')[0],\n });\n await nostrIdentity.initialize();\n await nostrIdentity.save();\n\n return {\n profile,\n nostrIdentity,\n created: true,\n };\n}\n","/**\n * OidcIdentityCollection - Collection for managing OIDC identity records\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { OidcIdentity } from '../models/OidcIdentity';\nimport type { Profile } from '../models/Profile';\n\nexport class OidcIdentityCollection extends SmrtCollection<OidcIdentity> {\n static readonly _itemClass = OidcIdentity;\n\n /**\n * Find identities for a profile\n */\n async findByProfile(profileId: string): Promise<OidcIdentity[]> {\n return await this.list({\n where: { profileId },\n });\n }\n\n /**\n * Find identity by issuer and subject\n */\n async findBySubject(\n issuer: string,\n subject: string,\n ): Promise<OidcIdentity | null> {\n return await this.findOne({\n where: { issuer, subject },\n });\n }\n\n /**\n * Find identities by provider\n */\n async findByProvider(provider: string): Promise<OidcIdentity[]> {\n return await this.list({\n where: { provider },\n });\n }\n\n /**\n * Link a new OIDC identity to a profile\n */\n async linkToProfile(\n profile: Profile,\n oidcData: {\n provider: string;\n issuer: string;\n subject: string;\n email?: string;\n },\n ): Promise<OidcIdentity> {\n // Check if already exists\n const existing = await this.findBySubject(\n oidcData.issuer,\n oidcData.subject,\n );\n if (existing) {\n // Update and return existing\n existing.lastUsedAt = new Date();\n if (oidcData.email) existing.email = oidcData.email;\n await existing.save();\n return existing;\n }\n\n // Create new\n const identity = new OidcIdentity({\n ...this.options,\n profileId: profile.id as string,\n provider: oidcData.provider,\n issuer: oidcData.issuer,\n subject: oidcData.subject,\n email: oidcData.email || '',\n });\n await identity.initialize();\n await identity.save();\n return identity;\n }\n\n /**\n * Unlink an OIDC identity from a profile\n */\n async unlinkFromProfile(\n profileId: string,\n issuer: string,\n subject: string,\n ): Promise<boolean> {\n const identity = await this.findOne({\n where: { profileId, issuer, subject },\n });\n\n if (identity) {\n await identity.delete();\n return true;\n }\n return false;\n }\n}\n","/**\n * ProfileTypeCollection - Collection manager for ProfileType objects\n *\n * Provides querying for profile type lookup table.\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { ProfileType } from '../models/ProfileType';\n\nexport class ProfileTypeCollection extends SmrtCollection<ProfileType> {\n static readonly _itemClass = ProfileType;\n\n /**\n * Get profile type by slug\n *\n * @param slug - The slug to search for\n * @returns ProfileType instance or null\n */\n async getBySlug(slug: string): Promise<ProfileType | null> {\n return await this.get({ slug });\n }\n\n /**\n * Get or create a profile type by slug\n *\n * @param slug - The slug to search for\n * @param defaults - Default values if creating\n * @returns ProfileType instance\n */\n async getOrCreateBySlug(\n slug: string,\n defaults: { name: string; description?: string },\n ): Promise<ProfileType> {\n const existing = await this.getBySlug(slug);\n if (existing) return existing;\n\n const profileType = await this.create({ slug, ...defaults });\n await profileType.save();\n return profileType;\n }\n}\n","/**\n * Profile subclasses for STI (Single Table Inheritance)\n *\n * These classes inherit from Profile and automatically use the shared\n * profiles table with _meta_type discriminator.\n */\n\nimport { smrt } from '@happyvertical/smrt-core';\nimport { Profile, type ProfileOptions } from './Profile';\n\n/**\n * Person profile type\n *\n * Represents individual people/users.\n */\n@smrt({\n tableStrategy: 'sti',\n})\nexport class Person extends Profile {\n constructor(options: ProfileOptions = {}) {\n super(options);\n // Person-specific initialization can go here\n }\n}\n\n/**\n * Organization profile type\n *\n * Represents companies, groups, institutions, and other organizational entities.\n */\n@smrt({\n tableStrategy: 'sti',\n})\nexport class Organization extends Profile {\n constructor(options: ProfileOptions = {}) {\n super(options);\n // Organization-specific initialization can go here\n }\n}\n\n/**\n * Bot profile type\n *\n * Represents automated agents, bots, and AI entities.\n */\n@smrt({\n tableStrategy: 'sti',\n})\nexport class Bot extends Profile {\n constructor(options: ProfileOptions = {}) {\n super(options);\n // Bot-specific initialization can go here\n }\n}\n"],"names":["MagicLinkTokenCollection","__decorateClass","Person","ProfileTypeCollection","ProfileType","OidcIdentityCollection","profile","oidcIdentity"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1292,13 +1292,20 @@ export declare class ProfileCollection extends SmrtCollection<Profile> {
1292
1292
  */
1293
1293
  findByTenant(tenantId: string): Promise<Profile[]>;
1294
1294
  /**
1295
- * Find all global profiles (without a tenant)
1295
+ * Find all global profiles (no tenant association).
1296
+ *
1297
+ * Routes through the shared tenant-global helper so it does not throw under
1298
+ * an active tenant context (an explicit `tenant_id IS NULL` filter would be
1299
+ * flagged as an isolation violation). (#1600)
1296
1300
  *
1297
1301
  * @returns Array of profiles with null tenantId
1298
1302
  */
1299
1303
  findGlobal(): Promise<Profile[]>;
1300
1304
  /**
1301
- * Find profiles belonging to a tenant plus all global profiles
1305
+ * Find profiles belonging to a tenant plus all global profiles.
1306
+ *
1307
+ * Fails closed if an active tenant context requests a different tenant's
1308
+ * rows; the admin/system path keeps the cross-tenant capability. (#1600)
1302
1309
  *
1303
1310
  * @param tenantId - The tenant UUID to include
1304
1311
  * @returns Array of tenant-specific and global profiles
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { B, M, a, O, b, c, P, d, e, f, g, h, i, p, r } from "./chunks/index-jFtOWsAV.js";
2
- import { a as a2, b as b2, P as P2, p as p2, s } from "./chunks/ProfileCollection-DU6wUJTO.js";
1
+ import { B, M, a, O, b, c, P, d, e, f, g, h, i, p, r } from "./chunks/index-DHLYp075.js";
2
+ import { a as a2, b as b2, P as P2, p as p2, s } from "./chunks/ProfileCollection-DQD1uJEc.js";
3
3
  import { a as a3, N, c as c2, b as b3, d as d2, f as f2, e as e2, g as g2, h as h2, i as i2, j, n, k, p as p3, l, s as s2, v, m } from "./chunks/NostrIdentityCollection-DadQBHWy.js";
4
4
  import { ApiKeyCollection } from "./chunks/ApiKeyCollection-B6Op817e.js";
5
5
  import { A, a as a4 } from "./chunks/AuditLogCollection-BYqCj0uE.js";
6
- import { P as P3, a as a5 } from "./chunks/ProfileAssetCollection-D_tk1kKG.js";
6
+ import { P as P3, a as a5 } from "./chunks/ProfileAssetCollection-ChX4kLjN.js";
7
7
  import { P as P4, a as a6 } from "./chunks/ProfileMetadataCollection-DEhmljMY.js";
8
8
  import { P as P5, a as a7 } from "./chunks/ProfileMetafieldCollection-DMKhSHXX.js";
9
9
  import { P as P6, a as a8 } from "./chunks/ProfileRelationshipCollection-C0IM8UQR.js";
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "timestamp": 1782238498241,
3
+ "timestamp": 1782248741982,
4
4
  "packageName": "@happyvertical/smrt-profiles",
5
- "packageVersion": "0.33.1",
5
+ "packageVersion": "0.34.1",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-profiles:ApiKeyCollection": {
8
8
  "name": "apikeycollection",
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-06-23T18:15:01.284Z",
3
+ "generatedAt": "2026-06-23T21:05:45.165Z",
4
4
  "packageName": "@happyvertical/smrt-profiles",
5
- "packageVersion": "0.33.1",
5
+ "packageVersion": "0.34.1",
6
6
  "sourceManifestPath": "dist/manifest.json",
7
7
  "agentDocPath": "AGENTS.md",
8
8
  "sourceHashes": {
9
- "manifest": "3747cc9c7c2dbecf02d6cb9ef7788b8a45296f47f5347abacda327a6bcb334bb",
10
- "packageJson": "e764551bd9a2234c4c9fabff8079c51ec8da899b58fcd542e22ca498c90dbb9f",
9
+ "manifest": "dfa0d463ed73c507184e1312d46c97a8fc7b91ca3c7b2ba1f9cf21f81d7ee61d",
10
+ "packageJson": "d7c3dfb511dc35fcbdf87ac569e7d47fae0c83ee6f4f3388d379f233083cec19",
11
11
  "agents": "3c18e895a2e065f761aabf31779c71a138f0a75261ab223464c3b2a5fbbe6b16"
12
12
  },
13
13
  "exports": [
package/dist/utils.js CHANGED
@@ -6,7 +6,7 @@ async function getProfileMetadata(profileId, options = {}) {
6
6
  return await metadataCollection.getMetadataObject(profileId);
7
7
  }
8
8
  async function setProfileMetadata(profileId, metafieldSlug, value, options = {}) {
9
- const { ProfileCollection } = await import("./chunks/ProfileCollection-DU6wUJTO.js").then((n) => n.d);
9
+ const { ProfileCollection } = await import("./chunks/ProfileCollection-DQD1uJEc.js").then((n) => n.d);
10
10
  const profileCollection = await ProfileCollection.create(options);
11
11
  const profile = await profileCollection.get({ id: profileId });
12
12
  if (!profile) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-profiles",
3
- "version": "0.33.1",
3
+ "version": "0.34.1",
4
4
  "description": "Profile management system with relationships, metadata, and reciprocal associations for SMRT framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -30,10 +30,10 @@
30
30
  "@happyvertical/utils": "^0.74.7",
31
31
  "@noble/curves": "^1.8.1",
32
32
  "bech32": "^2.0.0",
33
- "@happyvertical/smrt-core": "0.33.1",
34
- "@happyvertical/smrt-prompts": "0.33.1",
35
- "@happyvertical/smrt-tenancy": "0.33.1",
36
- "@happyvertical/smrt-assets": "0.33.1"
33
+ "@happyvertical/smrt-assets": "0.34.1",
34
+ "@happyvertical/smrt-tenancy": "0.34.1",
35
+ "@happyvertical/smrt-core": "0.34.1",
36
+ "@happyvertical/smrt-prompts": "0.34.1"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@faker-js/faker": "^10.2.0",
@@ -42,7 +42,7 @@
42
42
  "typescript": "^5.9.3",
43
43
  "vite": "^7.3.1",
44
44
  "vitest": "^4.0.17",
45
- "@happyvertical/smrt-vitest": "0.33.1"
45
+ "@happyvertical/smrt-vitest": "0.34.1"
46
46
  },
47
47
  "keywords": [
48
48
  "ai",
@@ -1 +0,0 @@
1
- {"version":3,"file":"ProfileCollection-DU6wUJTO.js","sources":["../../src/prompts.ts","../../src/models/ProfileType.ts","../../src/models/Profile.ts","../../src/collections/ProfileCollection.ts"],"sourcesContent":["/**\n * Prompt registrations for the @happyvertical/smrt-profiles package.\n *\n * Prompts are registered at module-load time via `definePrompt()` so that\n * tenant-aware overrides can be applied at call time via `resolvePrompt()`.\n *\n * Mirrors the pattern used by `@happyvertical/smrt-content` (see\n * `content-prompts.ts`) and `@happyvertical/smrt-facts` (see `prompts.ts`).\n */\n\nimport {\n definePrompt,\n type ResolvedPromptAI,\n} from '@happyvertical/smrt-prompts';\n\n// Bio generation only uses non-PII profile fields (name + description).\n// Email is intentionally NOT passed to the AI provider — see review on PR\n// #1209 — to minimize PII exposure. If a downstream tenant needs email in\n// the bio, they can override the template via PromptOverride.\nexport const smrtProfilesGenerateBioPrompt = definePrompt({\n key: 'smrtProfiles.profile.generateBio',\n template: `Write a short, professional bio for this person.\n\nProfile name: {profileName}\nProfile description: {profileDescription}\n\nReturn only the bio text, with no commentary or surrounding quotation marks.`,\n editable: {\n template: true,\n profile: true,\n model: true,\n params: true,\n },\n});\n\nexport function promptMessageOptions(ai: ResolvedPromptAI) {\n return {\n ...(ai.params || {}),\n ...(ai.model ? { model: ai.model } : {}),\n ...(typeof ai.temperature === 'number'\n ? { temperature: ai.temperature }\n : {}),\n ...(typeof ai.maxTokens === 'number' ? { maxTokens: ai.maxTokens } : {}),\n };\n}\n","/**\n * ProfileType model - Lookup table defining profile types\n *\n * Represents the nature of a profile (e.g., 'human', 'org', 'robot').\n * Uses UUID primary key with unique slug for human-readable lookups.\n */\n\nimport {\n field,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\n\nexport interface ProfileTypeOptions extends SmrtObjectOptions {\n slug?: string;\n name?: string;\n description?: string;\n tenantId?: string | null;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update'] },\n mcp: { include: ['list', 'get'] },\n cli: true,\n})\nexport class ProfileType extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n // id: UUID (auto-generated by SmrtObject)\n // slug is inherited from SmrtObject (auto-generated from name)\n @field({ required: true })\n name: string = '';\n\n description?: string;\n\n constructor(options: ProfileTypeOptions = {}) {\n super(options);\n if (options.name) this.name = options.name;\n if (options.description !== undefined)\n this.description = options.description;\n }\n\n /**\n * Convenience method for slug-based lookup\n *\n * @param slug - The slug to search for\n * @returns ProfileType instance or null if not found\n */\n static async getBySlug(_slug: string): Promise<ProfileType | null> {\n // Will be auto-implemented by SMRT\n return null;\n }\n}\n","/**\n * Profile model - Core entity representing any profile type\n *\n * Central table holding all primary entities with type, email, name, and description.\n * Uses UUID primary key with relationships to ProfileType for classification.\n */\n\nimport type { Asset } from '@happyvertical/smrt-assets';\nimport {\n assertValidOwnedAssetRelationship,\n assertValidOwnedAssetSortOrder,\n resolveOwnedAssetsById,\n} from '@happyvertical/smrt-assets';\n\nimport {\n field,\n foreignKey,\n oneToMany,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { resolvePrompt } from '@happyvertical/smrt-prompts';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport {\n promptMessageOptions,\n smrtProfilesGenerateBioPrompt,\n} from '../prompts';\nimport type { ProfileRelationship } from './ProfileRelationship';\nimport { ProfileType } from './ProfileType';\n\nexport interface ProfileOptions extends SmrtObjectOptions {\n typeId?: string;\n email?: string;\n name?: string;\n description?: string;\n tenantId?: string | null;\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get', 'create', 'update'] },\n cli: true,\n})\nexport class Profile extends SmrtObject {\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n // id: UUID (auto-generated by SmrtObject)\n @foreignKey('ProfileType', { required: true })\n typeId?: string; // References ProfileType.id\n\n @field({ unique: true })\n email?: string; // Optional email address\n\n @field({ required: true })\n name: string = ''; // Display name\n\n description?: string; // Short bio or description\n\n // Relationships (not stored as columns)\n @oneToMany('ProfileMetadata')\n metadata: any[] = [];\n\n // ProfileRelationship declares multiple foreign keys back to Profile\n // (fromProfileId / toProfileId / contextProfileId), so each oneToMany names\n // its inverse side explicitly. This both disambiguates `loadRelatedMany`\n // and gives the R10-generated `getRelationshipsFrom()` / `getRelationshipsTo()`\n // accessors the correct inverse foreign key.\n @oneToMany('ProfileRelationship', { foreignKey: 'fromProfileId' })\n relationshipsFrom: any[] = [];\n\n @oneToMany('ProfileRelationship', { foreignKey: 'toProfileId' })\n relationshipsTo: any[] = [];\n\n constructor(options: ProfileOptions = {}) {\n super(options);\n if (options.typeId !== undefined) this.typeId = options.typeId;\n if (options.email !== undefined) this.email = options.email;\n if (options.name) this.name = options.name;\n if (options.description !== undefined)\n this.description = options.description;\n }\n\n /**\n * Get the profile type slug for this profile\n *\n * @returns The slug of the profile type\n */\n async getTypeSlug(): Promise<string> {\n const type = await this.loadRelated('typeId');\n return type?.slug || '';\n }\n\n /**\n * Set the profile type by slug\n *\n * @param slug - The slug of the profile type\n * @throws Error if profile type not found\n */\n async setTypeBySlug(slug: string): Promise<void> {\n const type = await ProfileType.getBySlug(slug);\n if (!type) throw new Error(`Profile type '${slug}' not found`);\n this.typeId = type.id as string;\n }\n\n /**\n * Add metadata to this profile\n *\n * @param metafieldSlug - The slug of the metafield\n * @param value - The value to set\n */\n async addMetadata(metafieldSlug: string, value: any): Promise<void> {\n const { ProfileMetafieldCollection } = await import(\n '../collections/ProfileMetafieldCollection'\n );\n const { ProfileMetadataCollection } = await import(\n '../collections/ProfileMetadataCollection'\n );\n\n // Get or create metafield collection\n const metafieldCollection = await (\n ProfileMetafieldCollection as any\n ).create(this.options);\n\n // Find the metafield by slug\n const metafield = await metafieldCollection.getBySlug(metafieldSlug);\n if (!metafield) {\n throw new Error(`Metafield '${metafieldSlug}' not found`);\n }\n\n // Validate the value\n await metafield.validateValue(value);\n\n // Get or create metadata collection\n const metadataCollection = await (ProfileMetadataCollection as any).create(\n this.options,\n );\n\n // Check if metadata already exists\n const existing = await metadataCollection.list({\n where: { profileId: this.id, metafieldId: metafield.id },\n limit: 1,\n });\n\n if (existing.length > 0) {\n // Update existing\n const metadata = existing[0];\n metadata.value = String(value);\n await metadata.save();\n } else {\n // Create new\n const metadata = await metadataCollection.create({\n profileId: this.id,\n metafieldId: metafield.id,\n value: String(value),\n });\n await metadata.save();\n }\n }\n\n /**\n * Get all metadata for this profile as key-value object\n *\n * @returns Object with metafield slugs as keys\n */\n async getMetadata(): Promise<Record<string, any>> {\n const { ProfileMetadataCollection } = await import(\n '../collections/ProfileMetadataCollection'\n );\n\n const metadataCollection = await (ProfileMetadataCollection as any).create(\n this.options,\n );\n\n return await metadataCollection.getMetadataObject(this.id);\n }\n\n /**\n * Update multiple metadata values\n *\n * @param metadata - Object with metafield slugs as keys and values\n */\n async updateMetadata(metadata: Record<string, any>): Promise<void> {\n for (const [metafieldSlug, value] of Object.entries(metadata)) {\n await this.addMetadata(metafieldSlug, value);\n }\n }\n\n /**\n * Remove metadata by metafield slug\n *\n * @param metafieldSlug - The slug of the metafield to remove\n */\n async removeMetadata(metafieldSlug: string): Promise<void> {\n const { ProfileMetafieldCollection } = await import(\n '../collections/ProfileMetafieldCollection'\n );\n const { ProfileMetadataCollection } = await import(\n '../collections/ProfileMetadataCollection'\n );\n\n const metafieldCollection = await (\n ProfileMetafieldCollection as any\n ).create(this.options);\n\n const metafield = await metafieldCollection.getBySlug(metafieldSlug);\n if (!metafield) {\n throw new Error(`Metafield '${metafieldSlug}' not found`);\n }\n\n const metadataCollection = await (ProfileMetadataCollection as any).create(\n this.options,\n );\n\n const existing = await metadataCollection.list({\n where: { profileId: this.id, metafieldId: metafield.id },\n limit: 1,\n });\n\n if (existing.length > 0) {\n await existing[0].delete();\n }\n }\n\n private async getProfileAssetCollection() {\n const { ProfileAssetCollection } = await import(\n '../collections/ProfileAssetCollection'\n );\n return ProfileAssetCollection.create({ db: this.db });\n }\n async getAssets(relationship?: string): Promise<Asset[]> {\n if (!this.id) {\n return [];\n }\n\n const profileAssets = await this.getProfileAssetCollection();\n const linkedAssets = await profileAssets.byLeft(\n this.id,\n relationship ? { relationship } : {},\n );\n\n return resolveOwnedAssetsById(\n this.db,\n linkedAssets.map((link) => link.assetId),\n this.tenantId,\n );\n }\n\n async addAsset(\n asset: Asset,\n relationship = 'attachment',\n sortOrder = 0,\n ): Promise<void> {\n if (!this.id || !asset.id) {\n throw new Error('Cannot associate unsaved profile or asset');\n }\n\n assertValidOwnedAssetRelationship(relationship);\n assertValidOwnedAssetSortOrder(sortOrder);\n\n const profileAssets = await this.getProfileAssetCollection();\n await profileAssets.attach(this.id, asset.id, {\n relationship,\n sortOrder,\n tenantId: this.tenantId,\n });\n }\n\n async removeAsset(assetId: string, relationship?: string): Promise<void> {\n if (!this.id) {\n return;\n }\n\n const profileAssets = await this.getProfileAssetCollection();\n await profileAssets.detach(\n this.id,\n assetId,\n relationship ? { relationship } : {},\n );\n }\n\n /**\n * Add a relationship to another profile\n *\n * @param toProfile - The target profile\n * @param relationshipSlug - The type of relationship\n * @param contextProfile - Optional context profile for tertiary relationships\n */\n async addRelationship(\n toProfile: Profile,\n relationshipSlug: string,\n contextProfile?: Profile,\n ): Promise<void> {\n const { ProfileRelationshipTypeCollection } = await import(\n '../collections/ProfileRelationshipTypeCollection'\n );\n const { ProfileRelationshipCollection } = await import(\n '../collections/ProfileRelationshipCollection'\n );\n const { ProfileRelationshipType } = await import(\n './ProfileRelationshipType'\n );\n\n // Get relationship type\n const relationshipTypeCollection = await (\n ProfileRelationshipTypeCollection as any\n ).create(this.options);\n\n const relationshipType =\n await relationshipTypeCollection.getBySlug(relationshipSlug);\n if (!relationshipType) {\n throw new Error(`Relationship type '${relationshipSlug}' not found`);\n }\n\n // Check if relationship already exists\n const relationshipCollection = await (\n ProfileRelationshipCollection as any\n ).create(this.options);\n\n const exists = await relationshipCollection.exists(\n this.id,\n toProfile.id,\n relationshipType.id,\n );\n\n if (!exists) {\n // Create the relationship\n const relationship = await relationshipCollection.create({\n fromProfileId: this.id,\n toProfileId: toProfile.id,\n typeId: relationshipType.id,\n contextProfileId: contextProfile?.id,\n });\n await relationship.save();\n }\n\n // Handle reciprocal relationships\n if (relationshipType.reciprocal) {\n const handler =\n ProfileRelationshipType.getReciprocalHandler(relationshipSlug);\n if (handler) {\n await handler(this, toProfile, contextProfile);\n }\n }\n }\n\n /**\n * Get all relationships for this profile\n *\n * @param options - Filter options (typeSlug, direction)\n * @returns Array of ProfileRelationship instances\n */\n async getRelationships(options?: {\n typeSlug?: string;\n direction?: 'from' | 'to' | 'all';\n }): Promise<ProfileRelationship[]> {\n const { ProfileRelationshipCollection } = await import(\n '../collections/ProfileRelationshipCollection'\n );\n const { ProfileRelationshipTypeCollection } = await import(\n '../collections/ProfileRelationshipTypeCollection'\n );\n\n const relationshipCollection = await (\n ProfileRelationshipCollection as any\n ).create(this.options);\n\n const direction = options?.direction || 'all';\n\n // Get type ID if typeSlug is provided\n let typeId: string | undefined;\n if (options?.typeSlug) {\n const relationshipTypeCollection = await (\n ProfileRelationshipTypeCollection as any\n ).create(this.options);\n\n const relationshipType = await relationshipTypeCollection.getBySlug(\n options.typeSlug,\n );\n typeId = relationshipType?.id;\n }\n\n // Fetch relationships based on direction\n if (direction === 'from') {\n return await relationshipCollection.getFromProfile(this.id, typeId);\n } else if (direction === 'to') {\n return await relationshipCollection.getToProfile(this.id, typeId);\n } else {\n return await relationshipCollection.getForProfile(this.id, typeId);\n }\n }\n\n /**\n * Get related profiles\n *\n * @param relationshipSlug - Optional filter by relationship type slug\n * @returns Array of related Profile instances\n */\n async getRelatedProfiles(relationshipSlug?: string): Promise<Profile[]> {\n const { ProfileCollection } = await import(\n '../collections/ProfileCollection'\n );\n\n const relationships = await this.getRelationships({\n typeSlug: relationshipSlug,\n direction: 'all',\n });\n\n const profileCollection = await (ProfileCollection as any).create(\n this.options,\n );\n\n const relatedProfiles: Profile[] = [];\n const seenIds = new Set<string>();\n\n for (const relationship of relationships) {\n // Get the other profile (not this one)\n // Convert Field instances to strings for comparison\n const fromId = String(relationship.fromProfileId);\n const toId = String(relationship.toProfileId);\n const thisId = String(this.id);\n const otherId = fromId === thisId ? toId : fromId;\n\n if (!seenIds.has(otherId)) {\n seenIds.add(otherId);\n const profile = await profileCollection.get({ id: otherId });\n if (profile) {\n relatedProfiles.push(profile);\n }\n }\n }\n\n return relatedProfiles;\n }\n\n /**\n * Remove a relationship to another profile\n *\n * @param toProfile - The target profile\n * @param relationshipSlug - The type of relationship to remove\n */\n async removeRelationship(\n toProfile: Profile,\n relationshipSlug: string,\n ): Promise<void> {\n const { ProfileRelationshipTypeCollection } = await import(\n '../collections/ProfileRelationshipTypeCollection'\n );\n const { ProfileRelationshipCollection } = await import(\n '../collections/ProfileRelationshipCollection'\n );\n\n // Get relationship type\n const relationshipTypeCollection = await (\n ProfileRelationshipTypeCollection as any\n ).create(this.options);\n\n const relationshipType =\n await relationshipTypeCollection.getBySlug(relationshipSlug);\n if (!relationshipType) {\n throw new Error(`Relationship type '${relationshipSlug}' not found`);\n }\n\n // Find and delete the relationship\n const relationshipCollection = await (\n ProfileRelationshipCollection as any\n ).create(this.options);\n\n const relationships = await relationshipCollection.list({\n where: {\n fromProfileId: this.id,\n toProfileId: toProfile.id,\n typeId: relationshipType.id,\n },\n });\n\n for (const relationship of relationships) {\n await relationship.delete();\n }\n\n // Handle reciprocal relationships - delete the inverse\n if (relationshipType.reciprocal) {\n const inverseRelationships = await relationshipCollection.list({\n where: {\n fromProfileId: toProfile.id,\n toProfileId: this.id,\n typeId: relationshipType.id,\n },\n });\n\n for (const relationship of inverseRelationships) {\n await relationship.delete();\n }\n }\n }\n\n /**\n * AI-powered: Generate a professional bio for this profile\n *\n * Uses the `smrtProfiles.profile.generateBio` prompt registered via\n * `@happyvertical/smrt-prompts`, allowing tenant- or instance-level\n * overrides of the template, model, and parameters at runtime.\n *\n * @returns Generated bio text\n */\n async generateBio(): Promise<string> {\n // Resolve `db` from either the canonical `db` option or its `persistence`\n // alias. SmrtClass maps `persistence → db` lazily during `initialize()`,\n // so on a freshly-constructed Profile that has not yet been initialized,\n // `this.options.db` may be undefined while `this.options.persistence` is\n // set. Falling back here ensures stored app- and tenant-level prompt\n // overrides in `_smrt_prompt_overrides` are honored on the first call —\n // before `getAiClient()` triggers full initialization further below.\n const db = this.options.db ?? this.options.persistence;\n\n const resolvedPrompt = await resolvePrompt(\n smrtProfilesGenerateBioPrompt.key,\n {\n db,\n tenantId: this.tenantId,\n variables: {\n profileName: this.name || '',\n profileDescription: this.description || '',\n },\n },\n );\n\n const ai = await this.getAiClient();\n const response = await ai.message(\n resolvedPrompt.text,\n promptMessageOptions(resolvedPrompt.ai),\n );\n\n return response.trim();\n }\n\n /**\n * AI-powered: Check if profile matches criteria\n *\n * @param criteria - Criteria to match against\n * @returns True if matches criteria\n */\n async matches(criteria: string): Promise<boolean> {\n return await this.is(criteria);\n }\n\n /**\n * Find profiles by metadata key-value pair\n *\n * @param metafieldSlug - The metafield slug to search\n * @param value - The value to match\n * @returns Array of matching profiles\n */\n static async findByMetadata(\n _metafieldSlug: string,\n _value: any,\n ): Promise<Profile[]> {\n // Will be auto-implemented by SMRT\n return [];\n }\n\n /**\n * Find profiles by type slug\n *\n * @param typeSlug - The profile type slug\n * @returns Array of matching profiles\n */\n static async findByType(_typeSlug: string): Promise<Profile[]> {\n // Will be auto-implemented by SMRT\n return [];\n }\n\n /**\n * Find related profiles for a given profile\n *\n * @param profileId - The profile UUID\n * @param relationshipSlug - Optional filter by relationship type\n * @returns Array of related profiles\n */\n static async findRelated(\n _profileId: string,\n _relationshipSlug?: string,\n ): Promise<Profile[]> {\n // Will be auto-implemented by SMRT\n return [];\n }\n\n /**\n * Search profiles by email\n *\n * @param email - The email to search for\n * @returns Profile or null if not found\n */\n static async searchByEmail(_email: string): Promise<Profile | null> {\n // Will be auto-implemented by SMRT\n return null;\n }\n\n // ========================\n // Auth-related methods\n // ========================\n\n /**\n * Get all API keys for this profile\n *\n * @returns Array of API keys\n */\n async getApiKeys(): Promise<any[]> {\n const { ApiKeyCollection } = await import(\n '../collections/ApiKeyCollection'\n );\n const collection = await (ApiKeyCollection as any).create(this.options);\n return await collection.findByProfile(this.id as string);\n }\n\n /**\n * Get active (non-revoked, non-expired) API keys for this profile\n *\n * @returns Array of active API keys\n */\n async getActiveApiKeys(): Promise<any[]> {\n const { ApiKeyCollection } = await import(\n '../collections/ApiKeyCollection'\n );\n const collection = await (ApiKeyCollection as any).create(this.options);\n return await collection.findActiveByProfile(this.id as string);\n }\n\n /**\n * Generate a new API key for this profile\n *\n * @param options - Key options (name, scopes, expiration)\n * @returns The generated key (plaintext) and ApiKey record\n */\n async generateApiKey(options: {\n name: string;\n scopes?: string[];\n expiresAt?: Date | null;\n }): Promise<{ key: string; apiKey: any }> {\n const { ApiKey } = await import('./ApiKey');\n return await ApiKey.generate(this, {\n ...options,\n db: this.options?.db,\n });\n }\n\n /**\n * Get all OIDC identities linked to this profile\n *\n * @returns Array of OIDC identity records\n */\n async getOidcIdentities(): Promise<any[]> {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const collection = await (OidcIdentityCollection as any).create(\n this.options,\n );\n return await collection.findByProfile(this.id as string);\n }\n\n /**\n * Link a new OIDC identity to this profile\n *\n * @param oidcData - OIDC provider data\n * @returns The linked OIDC identity record\n */\n async linkOidcIdentity(oidcData: {\n provider: string;\n issuer: string;\n subject: string;\n email?: string;\n }): Promise<any> {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const collection = await (OidcIdentityCollection as any).create(\n this.options,\n );\n return await collection.linkToProfile(this, oidcData);\n }\n\n /**\n * Get all Nostr identities linked to this profile\n *\n * @returns Array of Nostr identity records\n */\n async getNostrIdentities(): Promise<any[]> {\n const { NostrIdentityCollection } = await import(\n '../collections/NostrIdentityCollection'\n );\n const collection = await (NostrIdentityCollection as any).create(\n this.options,\n );\n return await collection.findByProfile(this.id as string);\n }\n\n /**\n * Link a new Nostr identity to this profile\n *\n * @param nostrData - Nostr identity data (encrypted keypair)\n * @returns The linked Nostr identity record\n */\n async linkNostrIdentity(nostrData: {\n pubkey: string;\n encryptedPrivkey: string;\n encryptionIv: string;\n encryptionTag: string;\n email: string;\n nip05Username?: string;\n }): Promise<any> {\n const { NostrIdentityCollection } = await import(\n '../collections/NostrIdentityCollection'\n );\n const collection = await (NostrIdentityCollection as any).create(\n this.options,\n );\n return await collection.linkToProfile(this, nostrData);\n }\n\n /**\n * Get audit logs for actions performed by this profile\n *\n * @param limit - Maximum number of logs to return\n * @returns Array of audit log entries\n */\n async getAuditLogs(limit: number = 50): Promise<any[]> {\n const { AuditLogCollection } = await import(\n '../collections/AuditLogCollection'\n );\n const collection = await (AuditLogCollection as any).create(this.options);\n return await collection.getRecentActivity(this.id as string, limit);\n }\n\n /**\n * Record an audit log entry for an action by this profile\n *\n * @param options - Audit log options\n * @returns The created audit log entry\n */\n async recordAction(options: {\n action: string;\n resourceType: string;\n resourceId: string;\n source?: 'web' | 'cli' | 'ci' | 'webhook' | 'mcp';\n metadata?: Record<string, any>;\n onBehalfOf?: Profile | null;\n }): Promise<any> {\n const { AuditLogCollection } = await import(\n '../collections/AuditLogCollection'\n );\n const collection = await (AuditLogCollection as any).create(this.options);\n return await collection.record({\n profile: this,\n ...options,\n });\n }\n}\n","/**\n * ProfileCollection - Collection manager for Profile objects\n *\n * Provides advanced querying and batch operations for Profile entities.\n */\n\nimport type { Asset } from '@happyvertical/smrt-assets';\nimport {\n addOwnedAssetFromCollection,\n getOwnedAssetsFromCollection,\n removeOwnedAssetFromCollection,\n} from '@happyvertical/smrt-assets';\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Profile } from '../models/Profile';\n\nexport class ProfileCollection extends SmrtCollection<Profile> {\n static readonly _itemClass = Profile;\n\n /**\n * Find a profile by email address\n *\n * @param email - The email address to search for\n * @returns The matching profile or null\n */\n async findByEmail(email: string): Promise<Profile | null> {\n const normalizedEmail = email.toLowerCase();\n const profiles = await this.list({\n where: { email: normalizedEmail },\n limit: 1,\n });\n return profiles.length > 0 ? profiles[0] : null;\n }\n\n /**\n * Find profiles by type slug\n *\n * @param typeSlug - The profile type slug to filter by\n * @returns Array of matching profiles\n */\n async findByType(typeSlug: string): Promise<Profile[]> {\n // Will use eager loading when available\n const allProfiles = await this.list({});\n\n const filtered: Profile[] = [];\n for (const profile of allProfiles) {\n const slug = await profile.getTypeSlug();\n if (slug === typeSlug) {\n filtered.push(profile);\n }\n }\n\n return filtered;\n }\n\n /**\n * Batch get metadata for multiple profiles\n *\n * @param profileIds - Array of profile UUIDs\n * @returns Map of profile ID to metadata object\n */\n async batchGetMetadata(\n profileIds: string[],\n ): Promise<Map<string, Record<string, any>>> {\n const result = new Map<string, Record<string, any>>();\n\n for (const profileId of profileIds) {\n const profile = await this.get({ id: profileId });\n if (profile) {\n const metadata = await profile.getMetadata();\n result.set(profileId, metadata);\n }\n }\n\n return result;\n }\n\n /**\n * Batch update metadata for multiple profiles\n *\n * @param updates - Array of { profileId, data } objects\n */\n async batchUpdateMetadata(\n updates: Array<{ profileId: string; data: Record<string, any> }>,\n ): Promise<void> {\n for (const update of updates) {\n const profile = await this.get({ id: update.profileId });\n if (profile) {\n await profile.updateMetadata(update.data);\n }\n }\n }\n\n /**\n * Find related profiles for a given profile\n *\n * @param profileId - The profile UUID\n * @param relationshipSlug - Optional filter by relationship type\n * @returns Array of related profiles\n */\n async findRelated(\n profileId: string,\n relationshipSlug?: string,\n ): Promise<Profile[]> {\n const profile = await this.get({ id: profileId });\n if (!profile) return [];\n\n return await profile.getRelatedProfiles(relationshipSlug);\n }\n\n async getAssets(profileId: string, relationship?: string): Promise<Asset[]> {\n return getOwnedAssetsFromCollection(this, profileId, relationship);\n }\n\n async addAsset(\n profileId: string,\n asset: Asset,\n relationship = 'attachment',\n sortOrder = 0,\n ): Promise<void> {\n await addOwnedAssetFromCollection(\n this,\n 'Profile',\n profileId,\n asset,\n relationship,\n sortOrder,\n );\n }\n\n async removeAsset(\n profileId: string,\n assetId: string,\n relationship?: string,\n ): Promise<void> {\n await removeOwnedAssetFromCollection(\n this,\n 'Profile',\n profileId,\n assetId,\n relationship,\n );\n }\n\n /**\n * Get the relationship network for a profile up to a maximum depth\n *\n * @param profileId - The starting profile UUID\n * @param options - Configuration options\n * @returns Map of profile ID to depth level\n */\n async getRelationshipNetwork(\n profileId: string,\n options: { maxDepth?: number } = {},\n ): Promise<Map<string, number>> {\n const maxDepth = options.maxDepth || 2;\n const network = new Map<string, number>();\n const visited = new Set<string>();\n const queue: Array<{ id: string; depth: number }> = [\n { id: profileId, depth: 0 },\n ];\n\n while (queue.length > 0) {\n const current = queue.shift();\n if (!current) {\n break;\n }\n\n if (visited.has(current.id) || current.depth > maxDepth) {\n continue;\n }\n\n visited.add(current.id);\n network.set(current.id, current.depth);\n\n if (current.depth < maxDepth) {\n const related = await this.findRelated(current.id);\n for (const profile of related) {\n if (profile.id && !visited.has(profile.id)) {\n queue.push({ id: profile.id, depth: current.depth + 1 });\n }\n }\n }\n }\n\n return network;\n }\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Tenant-scoped helper methods\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Find all profiles belonging to a specific tenant\n *\n * @param tenantId - The tenant UUID to filter by\n * @returns Array of profiles for this tenant\n */\n async findByTenant(tenantId: string): Promise<Profile[]> {\n return this.list({ where: { tenantId } });\n }\n\n /**\n * Find all global profiles (without a tenant)\n *\n * @returns Array of profiles with null tenantId\n */\n async findGlobal(): Promise<Profile[]> {\n return this.list({ where: { tenantId: null } });\n }\n\n /**\n * Find profiles belonging to a tenant plus all global profiles\n *\n * @param tenantId - The tenant UUID to include\n * @returns Array of tenant-specific and global profiles\n */\n async findWithGlobals(tenantId: string): Promise<Profile[]> {\n return this.query(\n `SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,\n [tenantId],\n );\n }\n}\n"],"names":["__decorateClass","ProfileCollection","tenantId"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-jFtOWsAV.js","sources":["../../src/__smrt-register__.ts","../../src/models/MagicLinkToken.ts","../../src/collections/MagicLinkTokenCollection.ts","../../src/auth/magicLinkService.ts","../../src/auth/nip05Handler.ts","../../src/models/OidcIdentity.ts","../../src/auth/resolveIdentity.ts","../../src/collections/OidcIdentityCollection.ts","../../src/collections/ProfileTypeCollection.ts","../../src/models/ProfileTypes.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// `new URL('./manifest.json', import.meta.url)` resolves at runtime to the\n// manifest sitting next to this module's compiled output. Vite warns at build\n// time that it cannot pre-resolve the URL; that is the intended behavior —\n// the URL must resolve to dist/manifest.json at runtime, not be inlined.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * MagicLinkToken - Temporary tokens for email verification\n *\n * Stores hashed magic link tokens with expiration. Tokens are single-use\n * and automatically invalidated after verification or expiration.\n */\n\nimport { createHash, randomBytes } from 'node:crypto';\nimport {\n foreignKey,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport type { NostrIdentity } from './NostrIdentity';\n\nexport interface MagicLinkTokenOptions extends SmrtObjectOptions {\n nostrIdentityId?: string;\n tokenHash?: string;\n email?: string;\n expiresAt?: Date;\n usedAt?: Date | null;\n requestedFromIp?: string;\n createdAt?: Date;\n}\n\nexport interface GenerateTokenResult {\n /** The plaintext token (only available at creation time) */\n token: string;\n /** The MagicLinkToken record */\n magicLinkToken: MagicLinkToken;\n}\n\n/** Default token expiration: 15 minutes */\nconst DEFAULT_EXPIRATION_MINUTES = 15;\n\n@smrt({\n tableName: 'magic_link_tokens',\n api: { exclude: ['*'] }, // No API access - internal only\n mcp: { exclude: ['*'] },\n cli: { include: ['list'] }, // Admin visibility only\n})\nexport class MagicLinkToken extends SmrtObject {\n /**\n * Link to the NostrIdentity this token is for\n */\n @foreignKey('NostrIdentity', { required: true })\n nostrIdentityId?: string;\n\n /**\n * SHA-256 hash of the token (never store plaintext)\n */\n tokenHash: string = '';\n\n /**\n * Email this token was sent to\n */\n email: string = '';\n\n /**\n * When this token expires\n */\n expiresAt: Date = new Date(\n Date.now() + DEFAULT_EXPIRATION_MINUTES * 60 * 1000,\n );\n\n /**\n * When this token was used (null = unused)\n */\n usedAt: Date | null = null;\n\n /**\n * IP address that requested the token (for rate limiting)\n */\n requestedFromIp: string = '';\n\n /**\n * When this token was created (for rate limiting)\n */\n createdAt: Date = new Date();\n\n constructor(options: MagicLinkTokenOptions = {}) {\n super(options);\n if (options.nostrIdentityId) this.nostrIdentityId = options.nostrIdentityId;\n if (options.tokenHash) this.tokenHash = options.tokenHash;\n if (options.email) this.email = options.email;\n if (options.expiresAt) this.expiresAt = options.expiresAt;\n if (options.usedAt !== undefined) this.usedAt = options.usedAt;\n if (options.requestedFromIp) this.requestedFromIp = options.requestedFromIp;\n if (options.createdAt) this.createdAt = options.createdAt;\n }\n\n /**\n * Hash a token using SHA-256\n */\n static hashToken(token: string): string {\n return createHash('sha256').update(token).digest('hex');\n }\n\n /**\n * Generate a new magic link token\n * @param nostrIdentity - The identity this token is for\n * @param email - The email address to send to\n * @param options - Additional options\n */\n static async generate(\n nostrIdentity: NostrIdentity,\n email: string,\n options: {\n expiresInMinutes?: number;\n requestedFromIp?: string;\n db?: SmrtObjectOptions['db'];\n } = {},\n ): Promise<GenerateTokenResult> {\n // Generate 32 bytes of random data (256 bits of entropy)\n const tokenBytes = randomBytes(32);\n const token = tokenBytes.toString('base64url');\n const tokenHash = MagicLinkToken.hashToken(token);\n\n const expiresInMinutes =\n options.expiresInMinutes ?? DEFAULT_EXPIRATION_MINUTES;\n const expiresAt = new Date(Date.now() + expiresInMinutes * 60 * 1000);\n\n const magicLinkToken = new MagicLinkToken({\n db: options.db ?? nostrIdentity.options?.db,\n nostrIdentityId: nostrIdentity.id as string,\n tokenHash,\n email: email.toLowerCase(),\n expiresAt,\n requestedFromIp: options.requestedFromIp || '',\n });\n\n await magicLinkToken.initialize();\n await magicLinkToken.save();\n\n return { token, magicLinkToken };\n }\n\n /**\n * Verify a token and return the MagicLinkToken if valid\n * @param token - The plaintext token to verify\n * @param options - Database options\n * @returns The MagicLinkToken if valid, null otherwise\n */\n static async verify(\n token: string,\n options: SmrtObjectOptions = {},\n ): Promise<MagicLinkToken | null> {\n const tokenHash = MagicLinkToken.hashToken(token);\n\n const { MagicLinkTokenCollection } = await import(\n '../collections/MagicLinkTokenCollection'\n );\n const collection = await (MagicLinkTokenCollection as any).create(options);\n\n const magicLinkToken = await collection.findOne({\n where: { tokenHash },\n });\n\n if (!magicLinkToken) {\n return null;\n }\n\n // Check if valid\n if (!magicLinkToken.isValid()) {\n return null;\n }\n\n return magicLinkToken;\n }\n\n /**\n * Check if this token is valid (not expired and not used)\n */\n isValid(): boolean {\n if (this.usedAt !== null) {\n return false;\n }\n if (new Date() > this.expiresAt) {\n return false;\n }\n return true;\n }\n\n /**\n * Check if this token has expired\n */\n isExpired(): boolean {\n return new Date() > this.expiresAt;\n }\n\n /**\n * Check if this token has been used\n */\n isUsed(): boolean {\n return this.usedAt !== null;\n }\n\n /**\n * Mark this token as used\n */\n async markUsed(): Promise<void> {\n this.usedAt = new Date();\n await this.save();\n }\n\n /**\n * Get the NostrIdentity this token is for\n */\n async getNostrIdentity(): Promise<NostrIdentity | null> {\n return (await this.getRelated('nostrIdentityId')) as NostrIdentity | null;\n }\n\n /**\n * Get time remaining until expiration in seconds\n */\n getTimeRemainingSeconds(): number {\n const now = new Date();\n const diff = this.expiresAt.getTime() - now.getTime();\n return Math.max(0, Math.floor(diff / 1000));\n }\n}\n","/**\n * MagicLinkTokenCollection - Collection for managing magic link tokens\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { MagicLinkToken } from '../models/MagicLinkToken';\n\nexport class MagicLinkTokenCollection extends SmrtCollection<MagicLinkToken> {\n static readonly _itemClass = MagicLinkToken;\n\n /**\n * Find token by hash\n */\n async findByTokenHash(tokenHash: string): Promise<MagicLinkToken | null> {\n return await this.findOne({\n where: { tokenHash },\n });\n }\n\n /**\n * Find active (non-expired, non-used) tokens for an email\n */\n async findActiveForEmail(email: string): Promise<MagicLinkToken[]> {\n const tokens = await this.list({\n where: { email: email.toLowerCase() },\n });\n return tokens.filter((token) => token.isValid());\n }\n\n /**\n * Find tokens for a NostrIdentity\n */\n async findByIdentity(nostrIdentityId: string): Promise<MagicLinkToken[]> {\n return await this.list({\n where: { nostrIdentityId },\n });\n }\n\n /**\n * Count tokens created for an email within a time window (for rate limiting)\n * @param email - Email address\n * @param withinMinutes - Time window in minutes\n */\n async countRecentByEmail(\n email: string,\n withinMinutes: number,\n ): Promise<number> {\n const cutoff = new Date(Date.now() - withinMinutes * 60 * 1000);\n const tokens = await this.list({\n where: { email: email.toLowerCase() },\n });\n\n // Filter tokens created after cutoff\n // Note: We use createdAt from SmrtObject base\n return tokens.filter((token) => {\n const createdAt = (token as any).createdAt;\n return createdAt && new Date(createdAt) > cutoff;\n }).length;\n }\n\n /**\n * Count tokens created from an IP within a time window (for rate limiting)\n * @param ip - IP address\n * @param withinMinutes - Time window in minutes\n */\n async countRecentByIp(ip: string, withinMinutes: number): Promise<number> {\n const cutoff = new Date(Date.now() - withinMinutes * 60 * 1000);\n const tokens = await this.list({\n where: { requestedFromIp: ip },\n });\n\n // Filter tokens created after cutoff\n return tokens.filter((token) => {\n const createdAt = (token as any).createdAt;\n return createdAt && new Date(createdAt) > cutoff;\n }).length;\n }\n\n /**\n * Verify a token and return it if valid\n */\n async verify(token: string): Promise<MagicLinkToken | null> {\n const tokenHash = MagicLinkToken.hashToken(token);\n const magicLinkToken = await this.findByTokenHash(tokenHash);\n\n if (!magicLinkToken) {\n return null;\n }\n\n if (!magicLinkToken.isValid()) {\n return null;\n }\n\n return magicLinkToken;\n }\n\n /**\n * Clean up expired tokens by deleting them\n * @returns Number of tokens deleted\n */\n async cleanupExpired(): Promise<number> {\n const now = new Date();\n const allTokens = await this.list({});\n let deleted = 0;\n\n for (const token of allTokens) {\n if (token.isExpired() || token.isUsed()) {\n await token.delete();\n deleted++;\n }\n }\n\n return deleted;\n }\n\n /**\n * Revoke all tokens for a NostrIdentity (e.g., when identity is deleted)\n */\n async revokeForIdentity(nostrIdentityId: string): Promise<number> {\n const tokens = await this.findByIdentity(nostrIdentityId);\n let revoked = 0;\n\n for (const token of tokens) {\n if (!token.isUsed()) {\n await token.markUsed(); // Mark as used to invalidate\n revoked++;\n }\n }\n\n return revoked;\n }\n\n /**\n * Check if rate limit is exceeded for email\n * @param email - Email address\n * @param maxPerHour - Maximum tokens allowed per hour (default: 5)\n */\n async isRateLimitedByEmail(\n email: string,\n maxPerHour: number = 5,\n ): Promise<boolean> {\n const count = await this.countRecentByEmail(email, 60);\n return count >= maxPerHour;\n }\n\n /**\n * Check if rate limit is exceeded for IP\n * @param ip - IP address\n * @param maxPerHour - Maximum tokens allowed per hour (default: 10)\n */\n async isRateLimitedByIp(\n ip: string,\n maxPerHour: number = 10,\n ): Promise<boolean> {\n const count = await this.countRecentByIp(ip, 60);\n return count >= maxPerHour;\n }\n}\n","/**\n * Magic Link Service\n *\n * Handles the magic link authentication flow including:\n * - Token generation and storage\n * - Email sending (via callback)\n * - Token verification\n * - Rate limiting checks\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { MagicLinkTokenCollection } from '../collections/MagicLinkTokenCollection';\nimport { NostrIdentityCollection } from '../collections/NostrIdentityCollection';\nimport { MagicLinkToken } from '../models/MagicLinkToken';\nimport type { NostrIdentity } from '../models/NostrIdentity';\nimport type { Profile } from '../models/Profile';\nimport { encryptPrivkey, generateNostrKeypair } from './nostrCrypto';\n\nexport interface MagicLinkConfig {\n /** Base URL for magic links (e.g., \"https://example.com\") */\n baseUrl: string;\n /** Path for verification endpoint (default: \"/auth/verify\") */\n verifyPath?: string;\n /** Token expiration in minutes (default: 15) */\n expiresInMinutes?: number;\n /** Maximum tokens per email per hour (default: 5) */\n maxTokensPerEmailPerHour?: number;\n /** Maximum tokens per IP per hour (default: 10) */\n maxTokensPerIpPerHour?: number;\n /** Server master secret for encryption */\n masterSecret: string;\n /** Callback to send the email */\n sendEmail: (to: string, magicLink: string) => Promise<void>;\n /** Database options */\n db: SmrtObjectOptions['db'];\n}\n\nexport interface InitiateResult {\n success: boolean;\n /** The Nostr identity (existing or new) */\n nostrIdentity?: NostrIdentity;\n /** The profile (existing or new) */\n profile?: Profile;\n /** True if a new profile was created */\n created: boolean;\n /** Error message if success is false */\n error?: string;\n /** Error code for programmatic handling */\n errorCode?: 'RATE_LIMITED_EMAIL' | 'RATE_LIMITED_IP' | 'EMAIL_SEND_FAILED';\n}\n\nexport interface VerifyResult {\n success: boolean;\n /** The profile */\n profile?: Profile;\n /** The Nostr identity */\n nostrIdentity?: NostrIdentity;\n /** The decrypted keypair (only on success) */\n keypair?: {\n pubkey: string;\n privkey: string;\n npub: string;\n nsec: string;\n };\n /** Error message if success is false */\n error?: string;\n /** Error code for programmatic handling */\n errorCode?: 'INVALID_TOKEN' | 'EXPIRED_TOKEN' | 'USED_TOKEN' | 'NOT_FOUND';\n}\n\nexport interface MagicLinkService {\n /**\n * Initiate magic link flow - generates token and sends email\n */\n initiate(\n email: string,\n options?: {\n requestedFromIp?: string;\n nip05Username?: string;\n },\n ): Promise<InitiateResult>;\n\n /**\n * Verify a magic link token and return decrypted keypair\n */\n verify(token: string): Promise<VerifyResult>;\n}\n\n/**\n * Create a magic link service instance\n */\nexport function createMagicLinkService(\n config: MagicLinkConfig,\n): MagicLinkService {\n const {\n baseUrl,\n verifyPath = '/auth/verify',\n expiresInMinutes = 15,\n maxTokensPerEmailPerHour = 5,\n maxTokensPerIpPerHour = 10,\n masterSecret,\n sendEmail,\n db,\n } = config;\n\n return {\n async initiate(\n email: string,\n options: {\n requestedFromIp?: string;\n nip05Username?: string;\n } = {},\n ): Promise<InitiateResult> {\n const normalizedEmail = email.toLowerCase().trim();\n const { requestedFromIp, nip05Username } = options;\n\n // Create collections\n const identityCollection = await NostrIdentityCollection.create({ db });\n const tokenCollection = await MagicLinkTokenCollection.create({ db });\n\n // Rate limiting checks\n if (\n await tokenCollection.isRateLimitedByEmail(\n normalizedEmail,\n maxTokensPerEmailPerHour,\n )\n ) {\n return {\n success: false,\n created: false,\n error: 'Too many requests for this email. Please try again later.',\n errorCode: 'RATE_LIMITED_EMAIL',\n };\n }\n\n if (\n requestedFromIp &&\n (await tokenCollection.isRateLimitedByIp(\n requestedFromIp,\n maxTokensPerIpPerHour,\n ))\n ) {\n return {\n success: false,\n created: false,\n error: 'Too many requests from this IP. Please try again later.',\n errorCode: 'RATE_LIMITED_IP',\n };\n }\n\n // Check if identity exists\n let nostrIdentity = await identityCollection.findByEmail(normalizedEmail);\n let profile: Profile | null = null;\n let created = false;\n\n if (!nostrIdentity) {\n // Create new identity and profile\n const { Person } = await import('../models/ProfileTypes');\n\n // Create a new profile for this user (Person is an STI subclass of Profile)\n profile = new Person({\n db,\n email: normalizedEmail,\n name: normalizedEmail.split('@')[0], // Default name from email\n });\n await profile.initialize();\n await profile.save();\n\n // Generate keypair and encrypt\n const keypair = generateNostrKeypair();\n const encrypted = encryptPrivkey(keypair.privkey, masterSecret);\n\n // Create Nostr identity\n const { NostrIdentity: NostrIdentityClass } = await import(\n '../models/NostrIdentity'\n );\n nostrIdentity = new NostrIdentityClass({\n db,\n profileId: profile.id!,\n pubkey: keypair.pubkey,\n encryptedPrivkey: encrypted.ciphertext,\n encryptionIv: encrypted.iv,\n encryptionTag: encrypted.tag,\n email: normalizedEmail,\n nip05Username:\n nip05Username?.toLowerCase() || normalizedEmail.split('@')[0],\n });\n await nostrIdentity.initialize();\n await nostrIdentity.save();\n\n created = true;\n } else {\n profile = await nostrIdentity.getProfile();\n }\n\n // Generate magic link token\n const { token } = await MagicLinkToken.generate(\n nostrIdentity,\n normalizedEmail,\n {\n expiresInMinutes,\n requestedFromIp,\n db,\n },\n );\n\n // Build magic link URL\n const magicLink = `${baseUrl}${verifyPath}?token=${encodeURIComponent(token)}`;\n\n // Send email\n try {\n await sendEmail(normalizedEmail, magicLink);\n } catch (emailError) {\n return {\n success: false,\n created,\n nostrIdentity,\n profile: profile || undefined,\n error: 'Failed to send email. Please try again.',\n errorCode: 'EMAIL_SEND_FAILED',\n };\n }\n\n return {\n success: true,\n created,\n nostrIdentity,\n profile: profile || undefined,\n };\n },\n\n async verify(token: string): Promise<VerifyResult> {\n // Verify token\n const magicLinkToken = await MagicLinkToken.verify(token, { db });\n\n if (!magicLinkToken) {\n // Try to get more specific error\n const tokenHash = MagicLinkToken.hashToken(token);\n const tokenCollection = await MagicLinkTokenCollection.create({ db });\n const existingToken = await tokenCollection.findByTokenHash(tokenHash);\n\n if (!existingToken) {\n return {\n success: false,\n error: 'Invalid or expired token.',\n errorCode: 'NOT_FOUND',\n };\n }\n\n if (existingToken.isUsed()) {\n return {\n success: false,\n error: 'This link has already been used.',\n errorCode: 'USED_TOKEN',\n };\n }\n\n if (existingToken.isExpired()) {\n return {\n success: false,\n error: 'This link has expired. Please request a new one.',\n errorCode: 'EXPIRED_TOKEN',\n };\n }\n\n return {\n success: false,\n error: 'Invalid token.',\n errorCode: 'INVALID_TOKEN',\n };\n }\n\n // Mark token as used\n await magicLinkToken.markUsed();\n\n // Get the Nostr identity\n const nostrIdentity = await magicLinkToken.getNostrIdentity();\n if (!nostrIdentity) {\n return {\n success: false,\n error: 'Identity not found.',\n errorCode: 'NOT_FOUND',\n };\n }\n\n // Mark identity as verified\n await nostrIdentity.markVerified();\n\n // Get profile\n const profile = await nostrIdentity.getProfile();\n\n // Decrypt keypair\n const keypair = nostrIdentity.getKeypair(masterSecret);\n\n return {\n success: true,\n profile: profile || undefined,\n nostrIdentity,\n keypair,\n };\n },\n };\n}\n","/**\n * NIP-05 Handler\n *\n * Provides a helper for serving the /.well-known/nostr.json endpoint\n * for NIP-05 identity verification.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/05.md\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport {\n type Nip05Response,\n NostrIdentityCollection,\n} from '../collections/NostrIdentityCollection';\n\nexport interface Nip05HandlerConfig {\n /** Database options */\n db: SmrtObjectOptions['db'];\n /** Optional: Additional relays to include for all users */\n defaultRelays?: string[];\n /** Optional: Cache TTL in seconds (default: 300 = 5 minutes) */\n cacheTtlSeconds?: number;\n}\n\nexport interface Nip05Request {\n /** The 'name' query parameter from the request */\n name?: string | null;\n}\n\nexport interface Nip05HandlerResult {\n /** The NIP-05 response body */\n body: Nip05Response;\n /** Suggested headers for the response */\n headers: Record<string, string>;\n /** HTTP status code */\n status: number;\n}\n\n/**\n * Create a NIP-05 handler\n *\n * @example\n * // SvelteKit\n * import { createNip05Handler } from '@happyvertical/smrt-profiles';\n *\n * const handler = createNip05Handler({ db: { type: 'postgres', url: DATABASE_URL } });\n *\n * export async function GET({ url }) {\n * const result = await handler({ name: url.searchParams.get('name') });\n * return new Response(JSON.stringify(result.body), {\n * status: result.status,\n * headers: result.headers,\n * });\n * }\n *\n * @example\n * // Express\n * import { createNip05Handler } from '@happyvertical/smrt-profiles';\n *\n * const handler = createNip05Handler({ db: { type: 'postgres', url: DATABASE_URL } });\n *\n * app.get('/.well-known/nostr.json', async (req, res) => {\n * const result = await handler({ name: req.query.name });\n * res.status(result.status).set(result.headers).json(result.body);\n * });\n */\nexport function createNip05Handler(config: Nip05HandlerConfig) {\n const { db, defaultRelays = [], cacheTtlSeconds = 300 } = config;\n\n return async function handleNip05Request(\n request: Nip05Request,\n ): Promise<Nip05HandlerResult> {\n const { name } = request;\n\n const collection = await NostrIdentityCollection.create({ db });\n const response = await collection.getNip05Response(name || undefined);\n\n // Add default relays if configured\n if (defaultRelays.length > 0 && Object.keys(response.names).length > 0) {\n response.relays = {};\n for (const pubkey of Object.values(response.names)) {\n response.relays[pubkey] = defaultRelays;\n }\n }\n\n // If specific name requested but not found, return empty names\n if (name && Object.keys(response.names).length === 0) {\n return {\n body: { names: {} },\n headers: {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n 'Cache-Control': `public, max-age=${cacheTtlSeconds}`,\n },\n status: 200, // NIP-05 spec says return 200 with empty names, not 404\n };\n }\n\n return {\n body: response,\n headers: {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n 'Cache-Control': `public, max-age=${cacheTtlSeconds}`,\n },\n status: 200,\n };\n };\n}\n\n/**\n * Validate a NIP-05 identifier format\n * @param identifier - The identifier to validate (e.g., \"alice@example.com\")\n */\nexport function isValidNip05Identifier(identifier: string): boolean {\n // NIP-05 format: <local-part>@<domain>\n const parts = identifier.split('@');\n if (parts.length !== 2) {\n return false;\n }\n\n const [localPart, domain] = parts;\n\n // Local part: alphanumeric, _, -\n if (!/^[a-z0-9_-]+$/i.test(localPart)) {\n return false;\n }\n\n // Domain: basic domain validation\n if (!/^[a-z0-9.-]+\\.[a-z]{2,}$/i.test(domain)) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Parse a NIP-05 identifier into its parts\n * @param identifier - The identifier to parse (e.g., \"alice@example.com\")\n */\nexport function parseNip05Identifier(identifier: string): {\n localPart: string;\n domain: string;\n} | null {\n if (!isValidNip05Identifier(identifier)) {\n return null;\n }\n\n const [localPart, domain] = identifier.split('@');\n return { localPart, domain };\n}\n","/**\n * OidcIdentity - Links OIDC provider identities to Profile\n *\n * Stores the mapping between external OIDC providers (Keycloak, Google, GitHub)\n * and internal Profile records. Multiple identities can link to a single profile.\n */\n\nimport {\n field,\n foreignKey,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport type { Profile } from './Profile';\n\nexport interface OidcIdentityOptions extends SmrtObjectOptions {\n profileId?: string;\n provider?: string;\n issuer?: string;\n subject?: string;\n email?: string;\n lastUsedAt?: Date | null;\n}\n\n@smrt({\n tableName: 'oidc_identities',\n api: { exclude: ['delete'] },\n mcp: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n})\nexport class OidcIdentity extends SmrtObject {\n /**\n * Link to the Profile (Person, Organization, Bot)\n */\n @foreignKey('Profile', { required: true })\n profileId?: string;\n\n /**\n * Provider name (e.g., 'keycloak', 'google', 'github')\n */\n @field({ type: 'text' })\n provider: string = '';\n\n /**\n * OIDC issuer URL (e.g., https://keycloak.example.com/realms/bmp)\n */\n @field({ type: 'text' })\n issuer: string = '';\n\n /**\n * OIDC subject claim - unique identifier from the provider\n */\n @field({ type: 'text' })\n subject: string = '';\n\n /**\n * Cached email from the IdP (for display/lookup)\n */\n @field({ type: 'text' })\n email: string = '';\n\n /**\n * Last time this identity was used for authentication\n */\n @field({ type: 'datetime', nullable: true })\n lastUsedAt: Date | null = null;\n\n constructor(options: OidcIdentityOptions = {}) {\n super(options);\n if (options.profileId) this.profileId = options.profileId;\n if (options.provider) this.provider = options.provider;\n if (options.issuer) this.issuer = options.issuer;\n if (options.subject) this.subject = options.subject;\n if (options.email) this.email = options.email;\n if (options.lastUsedAt !== undefined) this.lastUsedAt = options.lastUsedAt;\n }\n\n /**\n * Get the linked Profile\n */\n async getProfile(): Promise<Profile | null> {\n return (await this.getRelated('profileId')) as Profile | null;\n }\n\n /**\n * Find identity by issuer and subject\n */\n static async findBySubject(\n issuer: string,\n subject: string,\n options: SmrtObjectOptions = {},\n ): Promise<OidcIdentity | null> {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const collection = await (OidcIdentityCollection as any).create(options);\n return await collection.findOne({\n where: { issuer, subject },\n });\n }\n\n /**\n * Find or create identity for a profile\n */\n static async findOrCreate(\n profile: Profile,\n oidcData: {\n provider: string;\n issuer: string;\n subject: string;\n email?: string;\n },\n options: SmrtObjectOptions = {},\n ): Promise<OidcIdentity> {\n const existing = await OidcIdentity.findBySubject(\n oidcData.issuer,\n oidcData.subject,\n options,\n );\n\n if (existing) {\n // Update last used\n existing.lastUsedAt = new Date();\n if (oidcData.email) existing.email = oidcData.email;\n await existing.save();\n return existing;\n }\n\n // Create new\n const identity = new OidcIdentity({\n ...options,\n profileId: profile.id as string,\n provider: oidcData.provider,\n issuer: oidcData.issuer,\n subject: oidcData.subject,\n email: oidcData.email || '',\n });\n await identity.initialize();\n await identity.save();\n return identity;\n }\n\n /**\n * Record usage of this identity\n */\n async recordUsage(): Promise<void> {\n this.lastUsedAt = new Date();\n await this.save();\n }\n}\n","/**\n * resolveIdentity - Resolves authentication context to a Profile\n *\n * Used by apps (blindmanpress.com) to resolve incoming auth to a Profile.\n * Modules (aedile, praeco) should receive the resolved profile from the app.\n *\n * Resolution order:\n * 1. API key header → ApiKey → Profile\n * 2. OIDC session → OidcIdentity → Profile\n * 3. Nostr auth → NostrIdentity → Profile\n * 4. Actor header (CI pass-through) → Profile lookup\n */\n\nimport type { SmrtObjectOptions } from '@happyvertical/smrt-core';\nimport { ApiKey } from '../models/ApiKey';\nimport { NostrIdentity } from '../models/NostrIdentity';\nimport { OidcIdentity } from '../models/OidcIdentity';\nimport type { Profile } from '../models/Profile';\nimport { type NostrEvent, verifyAuthEvent } from './nostrCrypto';\n\n/**\n * Context provided to resolveIdentity\n */\nexport interface AuthContext {\n /**\n * API key from X-API-Key header or similar\n */\n apiKey?: string | null;\n\n /**\n * OIDC session data (from @auth/sveltekit or similar)\n */\n oidcSession?: {\n sub?: string;\n iss?: string;\n email?: string;\n name?: string;\n } | null;\n\n /**\n * Actor identifier for CI pass-through identity\n * Usually the GitHub actor (username) who triggered the workflow\n */\n actor?: string | null;\n\n /**\n * Nostr authentication data (NIP-42 style)\n */\n nostrAuth?: {\n /** Signed Nostr event for authentication */\n event: NostrEvent;\n /** Expected challenge (to prevent replay attacks) */\n challenge: string;\n } | null;\n\n /**\n * Database/persistence options\n */\n db?: SmrtObjectOptions['db'];\n}\n\n/**\n * Result of identity resolution\n */\nexport interface ResolveIdentityResult {\n /**\n * The resolved profile, or null if not authenticated\n */\n profile: Profile | null;\n\n /**\n * How the identity was resolved\n */\n source: 'api_key' | 'oidc' | 'nostr' | 'actor' | 'none';\n\n /**\n * The API key record if authenticated via API key\n */\n apiKey?: ApiKey;\n\n /**\n * The OIDC identity record if authenticated via OIDC\n */\n oidcIdentity?: OidcIdentity;\n\n /**\n * The Nostr identity record if authenticated via Nostr\n */\n nostrIdentity?: NostrIdentity;\n}\n\n/**\n * Resolve authentication context to a Profile\n *\n * @param context - The authentication context from the request\n * @returns The resolved profile and metadata\n *\n * @example\n * ```typescript\n * // In SvelteKit hooks.server.ts\n * import { resolveIdentity } from '@happyvertical/smrt-profiles';\n *\n * const identityMiddleware: Handle = async ({ event, resolve }) => {\n * const session = await event.locals.auth();\n *\n * const { profile, source } = await resolveIdentity({\n * oidcSession: session,\n * apiKey: event.request.headers.get('X-API-Key'),\n * actor: event.request.headers.get('X-Actor'),\n * db: { type: 'postgres', url: DATABASE_URL },\n * });\n *\n * event.locals.profile = profile;\n * event.locals.authSource = source;\n *\n * return resolve(event);\n * };\n * ```\n */\nexport async function resolveIdentity(\n context: AuthContext,\n): Promise<ResolveIdentityResult> {\n const options: SmrtObjectOptions = { db: context.db };\n\n // 1. Check API key header first (highest priority for programmatic access)\n if (context.apiKey) {\n const apiKey = await ApiKey.verify(context.apiKey, options);\n if (apiKey) {\n const profile = await apiKey.getProfile();\n if (profile) {\n return {\n profile,\n source: 'api_key',\n apiKey,\n };\n }\n }\n }\n\n // 2. Check OIDC session (web users)\n if (context.oidcSession?.sub && context.oidcSession?.iss) {\n const oidcIdentity = await OidcIdentity.findBySubject(\n context.oidcSession.iss,\n context.oidcSession.sub,\n options,\n );\n\n if (oidcIdentity) {\n // Record usage\n await oidcIdentity.recordUsage();\n\n const profile = await oidcIdentity.getProfile();\n if (profile) {\n return {\n profile,\n source: 'oidc',\n oidcIdentity,\n };\n }\n }\n }\n\n // 3. Check Nostr auth (NIP-42 style signed event)\n if (context.nostrAuth?.event && context.nostrAuth?.challenge) {\n const { event, challenge } = context.nostrAuth;\n\n // Verify the auth event\n const verifyResult = verifyAuthEvent(event, challenge);\n if (verifyResult.valid) {\n // Look up identity by public key\n const nostrIdentity = await NostrIdentity.findByPubkey(\n event.pubkey,\n options,\n );\n\n if (nostrIdentity) {\n // Record usage\n await nostrIdentity.recordUsage();\n\n const profile = await nostrIdentity.getProfile();\n if (profile) {\n return {\n profile,\n source: 'nostr',\n nostrIdentity,\n };\n }\n }\n }\n }\n\n // 4. Check actor for CI pass-through (look up by metadata)\n if (context.actor) {\n const profile = await findProfileByExternalId(\n 'github',\n context.actor,\n options,\n );\n if (profile) {\n return {\n profile,\n source: 'actor',\n };\n }\n }\n\n // No authentication found\n return {\n profile: null,\n source: 'none',\n };\n}\n\n/**\n * Find a profile by external ID (e.g., GitHub username)\n *\n * This looks up profiles by their linked external identities or metadata.\n *\n * @param provider - The external provider (e.g., 'github')\n * @param externalId - The external identifier (e.g., GitHub username)\n * @param options - Database options\n * @returns The profile or null\n */\nasync function findProfileByExternalId(\n provider: string,\n externalId: string,\n options: SmrtObjectOptions,\n): Promise<Profile | null> {\n // First check OIDC identities (if they use the provider)\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n\n const oidcCollection = await (OidcIdentityCollection as any).create(options);\n const identities = await oidcCollection.findByProvider(provider);\n\n for (const identity of identities) {\n // Check if the subject matches the external ID\n if (\n identity.subject === externalId ||\n identity.email?.includes(externalId)\n ) {\n const profile = await identity.getProfile();\n if (profile) return profile;\n }\n }\n\n // Could also check profile metadata for external IDs\n // This would require a standardized metadata field like 'github_username'\n const { ProfileCollection } = await import(\n '../collections/ProfileCollection'\n );\n const profileCollection = await (ProfileCollection as any).create(options);\n\n // Try to find by email that looks like a GitHub noreply\n const profiles = await profileCollection.list({\n where: {\n email: `${externalId}@users.noreply.github.com`,\n },\n limit: 1,\n });\n\n if (profiles.length > 0) {\n return profiles[0];\n }\n\n return null;\n}\n\n/**\n * Create a profile from OIDC claims if it doesn't exist\n *\n * Supports email-based account linking: if a user signs in with Google\n * and later with GitHub using the same email, they get the same profile.\n *\n * Resolution order:\n * 1. If OIDC identity (iss + sub) already exists → return linked profile\n * 2. If verified email provided, check if profile with same email exists → link new identity\n * 3. Otherwise, create new profile + identity\n *\n * Security considerations:\n * - Email-based linking only occurs when `email_verified` is true. This prevents\n * attackers from claiming unverified emails to hijack accounts.\n * - Linking is automatic and irreversible through this API. Multiple OIDC\n * identities from different providers sharing the same verified email will\n * be associated to the same Profile.\n * - If an OIDC provider does not supply an email, or the email changes later,\n * existing links are not automatically updated. New sign-ins without an email\n * or with a different email may result in a new Profile being created.\n * - This function trusts the OIDC provider to assert correct email_verified status.\n * Only use with trusted providers.\n *\n * @param claims - OIDC token claims\n * @param provider - Provider name (e.g., 'keycloak', 'google', 'github')\n * @param options - Database options\n * @returns The created or existing profile with linked OIDC identity\n */\nexport async function createProfileFromOidc(\n claims: {\n sub: string;\n iss: string;\n email?: string;\n email_verified?: boolean;\n name?: string;\n preferred_username?: string;\n },\n provider: string,\n options: SmrtObjectOptions,\n): Promise<{ profile: Profile; oidcIdentity: OidcIdentity; created: boolean }> {\n // 1. If OIDC identity already exists for this issuer+subject, return linked profile\n const existingIdentity = await OidcIdentity.findBySubject(\n claims.iss,\n claims.sub,\n options,\n );\n\n if (existingIdentity) {\n const profile = await existingIdentity.getProfile();\n if (profile) {\n // Update identity with latest claims\n if (claims.email) existingIdentity.email = claims.email;\n existingIdentity.lastUsedAt = new Date();\n await existingIdentity.save();\n\n return {\n profile,\n oidcIdentity: existingIdentity,\n created: false,\n };\n }\n }\n\n // 2. Email-based account linking: only link if email is verified (security)\n if (claims.email && claims.email_verified) {\n const { ProfileCollection } = await import(\n '../collections/ProfileCollection'\n );\n\n const profileCollection = await (ProfileCollection as any).create(options);\n const existingProfile = await profileCollection.findByEmail(claims.email);\n\n if (existingProfile) {\n // Link new OIDC identity to existing profile (email-based linking)\n const oidcIdentity = await OidcIdentity.findOrCreate(\n existingProfile,\n {\n provider,\n issuer: claims.iss,\n subject: claims.sub,\n email: claims.email,\n },\n options,\n );\n\n return {\n profile: existingProfile,\n oidcIdentity,\n created: false,\n };\n }\n }\n\n // 3. Create new profile\n const { Person } = await import('../models/ProfileTypes');\n const { ProfileTypeCollection } = await import(\n '../collections/ProfileTypeCollection'\n );\n\n // Get or create the 'person' type\n const typeCollection = await (ProfileTypeCollection as any).create(options);\n let personType = await typeCollection.getBySlug('person');\n\n if (!personType) {\n // Create the person type if it doesn't exist\n const { ProfileType } = await import('../models/ProfileType');\n personType = new ProfileType({\n ...options,\n slug: 'person',\n name: 'Person',\n description: 'Individual person profile',\n });\n await personType.initialize();\n await personType.save();\n }\n\n const profile = new Person({\n ...options,\n typeId: personType.id as string,\n email: claims.email || '',\n name: claims.name || claims.preferred_username || claims.sub,\n });\n await profile.initialize();\n await profile.save();\n\n // Link OIDC identity\n const oidcIdentity = await OidcIdentity.findOrCreate(\n profile,\n {\n provider,\n issuer: claims.iss,\n subject: claims.sub,\n email: claims.email,\n },\n options,\n );\n\n return {\n profile,\n oidcIdentity,\n created: true,\n };\n}\n\n/**\n * Create a profile from Nostr identity (used by magic link service)\n *\n * This is typically called internally by the magic link service,\n * but can be used directly if needed.\n *\n * @param email - Email address for the profile\n * @param nostrData - Encrypted Nostr keypair data\n * @param options - Database options\n * @returns The created profile with linked Nostr identity\n */\nexport async function createProfileFromNostr(\n email: string,\n nostrData: {\n pubkey: string;\n encryptedPrivkey: string;\n encryptionIv: string;\n encryptionTag: string;\n nip05Username?: string;\n },\n options: SmrtObjectOptions,\n): Promise<{\n profile: Profile;\n nostrIdentity: NostrIdentity;\n created: boolean;\n}> {\n const normalizedEmail = email.toLowerCase();\n\n // Check if Nostr identity already exists\n const existingIdentity = await NostrIdentity.findByEmail(\n normalizedEmail,\n options,\n );\n\n if (existingIdentity) {\n const profile = await existingIdentity.getProfile();\n if (profile) {\n return {\n profile,\n nostrIdentity: existingIdentity,\n created: false,\n };\n }\n }\n\n // Create new profile\n const { Person } = await import('../models/ProfileTypes');\n const { ProfileTypeCollection } = await import(\n '../collections/ProfileTypeCollection'\n );\n\n // Get or create the 'person' type\n const typeCollection = await (ProfileTypeCollection as any).create(options);\n let personType = await typeCollection.getBySlug('person');\n\n if (!personType) {\n const { ProfileType } = await import('../models/ProfileType');\n personType = new ProfileType({\n ...options,\n slug: 'person',\n name: 'Person',\n description: 'Individual person profile',\n });\n await personType.initialize();\n await personType.save();\n }\n\n const profile = new Person({\n ...options,\n typeId: personType.id as string,\n email: normalizedEmail,\n name: normalizedEmail.split('@')[0], // Default name from email\n });\n await profile.initialize();\n await profile.save();\n\n // Create Nostr identity\n const nostrIdentity = new NostrIdentity({\n ...options,\n profileId: profile.id as string,\n pubkey: nostrData.pubkey,\n encryptedPrivkey: nostrData.encryptedPrivkey,\n encryptionIv: nostrData.encryptionIv,\n encryptionTag: nostrData.encryptionTag,\n email: normalizedEmail,\n nip05Username:\n nostrData.nip05Username?.toLowerCase() || normalizedEmail.split('@')[0],\n });\n await nostrIdentity.initialize();\n await nostrIdentity.save();\n\n return {\n profile,\n nostrIdentity,\n created: true,\n };\n}\n","/**\n * OidcIdentityCollection - Collection for managing OIDC identity records\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { OidcIdentity } from '../models/OidcIdentity';\nimport type { Profile } from '../models/Profile';\n\nexport class OidcIdentityCollection extends SmrtCollection<OidcIdentity> {\n static readonly _itemClass = OidcIdentity;\n\n /**\n * Find identities for a profile\n */\n async findByProfile(profileId: string): Promise<OidcIdentity[]> {\n return await this.list({\n where: { profileId },\n });\n }\n\n /**\n * Find identity by issuer and subject\n */\n async findBySubject(\n issuer: string,\n subject: string,\n ): Promise<OidcIdentity | null> {\n return await this.findOne({\n where: { issuer, subject },\n });\n }\n\n /**\n * Find identities by provider\n */\n async findByProvider(provider: string): Promise<OidcIdentity[]> {\n return await this.list({\n where: { provider },\n });\n }\n\n /**\n * Link a new OIDC identity to a profile\n */\n async linkToProfile(\n profile: Profile,\n oidcData: {\n provider: string;\n issuer: string;\n subject: string;\n email?: string;\n },\n ): Promise<OidcIdentity> {\n // Check if already exists\n const existing = await this.findBySubject(\n oidcData.issuer,\n oidcData.subject,\n );\n if (existing) {\n // Update and return existing\n existing.lastUsedAt = new Date();\n if (oidcData.email) existing.email = oidcData.email;\n await existing.save();\n return existing;\n }\n\n // Create new\n const identity = new OidcIdentity({\n ...this.options,\n profileId: profile.id as string,\n provider: oidcData.provider,\n issuer: oidcData.issuer,\n subject: oidcData.subject,\n email: oidcData.email || '',\n });\n await identity.initialize();\n await identity.save();\n return identity;\n }\n\n /**\n * Unlink an OIDC identity from a profile\n */\n async unlinkFromProfile(\n profileId: string,\n issuer: string,\n subject: string,\n ): Promise<boolean> {\n const identity = await this.findOne({\n where: { profileId, issuer, subject },\n });\n\n if (identity) {\n await identity.delete();\n return true;\n }\n return false;\n }\n}\n","/**\n * ProfileTypeCollection - Collection manager for ProfileType objects\n *\n * Provides querying for profile type lookup table.\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { ProfileType } from '../models/ProfileType';\n\nexport class ProfileTypeCollection extends SmrtCollection<ProfileType> {\n static readonly _itemClass = ProfileType;\n\n /**\n * Get profile type by slug\n *\n * @param slug - The slug to search for\n * @returns ProfileType instance or null\n */\n async getBySlug(slug: string): Promise<ProfileType | null> {\n return await this.get({ slug });\n }\n\n /**\n * Get or create a profile type by slug\n *\n * @param slug - The slug to search for\n * @param defaults - Default values if creating\n * @returns ProfileType instance\n */\n async getOrCreateBySlug(\n slug: string,\n defaults: { name: string; description?: string },\n ): Promise<ProfileType> {\n const existing = await this.getBySlug(slug);\n if (existing) return existing;\n\n const profileType = await this.create({ slug, ...defaults });\n await profileType.save();\n return profileType;\n }\n}\n","/**\n * Profile subclasses for STI (Single Table Inheritance)\n *\n * These classes inherit from Profile and automatically use the shared\n * profiles table with _meta_type discriminator.\n */\n\nimport { smrt } from '@happyvertical/smrt-core';\nimport { Profile, type ProfileOptions } from './Profile';\n\n/**\n * Person profile type\n *\n * Represents individual people/users.\n */\n@smrt({\n tableStrategy: 'sti',\n})\nexport class Person extends Profile {\n constructor(options: ProfileOptions = {}) {\n super(options);\n // Person-specific initialization can go here\n }\n}\n\n/**\n * Organization profile type\n *\n * Represents companies, groups, institutions, and other organizational entities.\n */\n@smrt({\n tableStrategy: 'sti',\n})\nexport class Organization extends Profile {\n constructor(options: ProfileOptions = {}) {\n super(options);\n // Organization-specific initialization can go here\n }\n}\n\n/**\n * Bot profile type\n *\n * Represents automated agents, bots, and AI entities.\n */\n@smrt({\n tableStrategy: 'sti',\n})\nexport class Bot extends Profile {\n constructor(options: ProfileOptions = {}) {\n super(options);\n // Bot-specific initialization can go here\n }\n}\n"],"names":["MagicLinkTokenCollection","__decorateClass","Person","OidcIdentityCollection","profile","oidcIdentity","ProfileTypeCollection","ProfileType"],"mappings":"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"}