@happyvertical/smrt-profiles 0.38.2 → 0.38.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -116,7 +116,7 @@ var ApiKey = class extends SmrtObject {
116
116
  */
117
117
  static async verify(key, options = {}) {
118
118
  const keyHash = ApiKey.hashKey(key);
119
- const { ApiKeyCollection } = await import("./ApiKeyCollection-PwVs0qDb.js").then((n) => n.n);
119
+ const { ApiKeyCollection } = await import("./ApiKeyCollection-DNhDkS7O.js").then((n) => n.n);
120
120
  const apiKey = await (await ApiKeyCollection.create(options)).findOne({ where: { keyHash } });
121
121
  if (!apiKey) return null;
122
122
  if (!apiKey.isValid()) return null;
@@ -142,4 +142,4 @@ ApiKey = __decorateClass([smrt({
142
142
  //#endregion
143
143
  export { ApiKey_exports as n, ApiKey as t };
144
144
 
145
- //# sourceMappingURL=ApiKey-3DlNEc91.js.map
145
+ //# sourceMappingURL=ApiKey-BrNyaaZN.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ApiKey-3DlNEc91.js","names":[],"sources":["../../src/models/ApiKey.ts"],"sourcesContent":["/**\n * ApiKey - API keys for CLI and CI authentication\n *\n * Provides secure API key management with hashing, scopes, and expiration.\n * Keys are stored as hashes - the plaintext is only returned on creation.\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 { Profile } from './Profile';\n\nexport interface ApiKeyOptions extends SmrtObjectOptions {\n profileId?: string;\n name?: string;\n scopes?: string[];\n expiresAt?: Date | null;\n}\n\nexport interface GenerateKeyResult {\n key: string;\n apiKey: ApiKey;\n}\n\n@smrt({\n tableName: 'api_keys',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n // create/revoke are admin operations invoked in-process via the CLI; only\n // read-only list/get are exposed over HTTP.\n cli: { include: ['list', 'get', 'create', 'revoke'], skipApiCheck: true },\n})\nexport class ApiKey extends SmrtObject {\n /**\n * Link to the Profile (Person, Organization, Bot)\n */\n @foreignKey('Profile', { required: true })\n profileId?: string;\n\n /**\n * SHA-256 hash of the API key\n */\n keyHash: string = '';\n\n /**\n * First 8 characters of the key for identification (e.g., \"sk_live_a1b2...\")\n */\n keyPrefix: string = '';\n\n /**\n * Human-readable name for the key (e.g., \"CI Bot\", \"Local CLI\")\n */\n name: string = '';\n\n /**\n * Scopes/permissions for this key\n */\n scopes: string[] = [];\n\n /**\n * Last time this key was used\n */\n lastUsedAt: Date | null = null;\n\n /**\n * When this key expires (null = never)\n */\n expiresAt: Date | null = null;\n\n /**\n * When this key was revoked (null = active)\n */\n revokedAt: Date | null = null;\n\n constructor(options: ApiKeyOptions = {}) {\n super(options);\n if (options.profileId) this.profileId = options.profileId;\n if (options.name) this.name = options.name;\n if (options.scopes) this.scopes = options.scopes;\n if (options.expiresAt !== undefined) this.expiresAt = options.expiresAt;\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 * Check if this key is valid (not expired, not revoked)\n */\n isValid(): boolean {\n if (this.revokedAt) return false;\n if (this.expiresAt && this.expiresAt < new Date()) return false;\n return true;\n }\n\n /**\n * Check if this key has a specific scope\n */\n hasScope(scope: string): boolean {\n return this.scopes.includes(scope) || this.scopes.includes('*');\n }\n\n /**\n * Revoke this key\n */\n async revoke(): Promise<void> {\n this.revokedAt = new Date();\n await this.save();\n }\n\n /**\n * Record usage of this key\n */\n async recordUsage(): Promise<void> {\n this.lastUsedAt = new Date();\n await this.save();\n }\n\n /**\n * Hash an API key\n */\n static hashKey(key: string): string {\n return createHash('sha256').update(key).digest('hex');\n }\n\n /**\n * Generate a new API key for a profile\n */\n static async generate(\n profile: Profile,\n options: {\n name: string;\n scopes?: string[];\n expiresAt?: Date | null;\n db?: SmrtObjectOptions['db'];\n },\n ): Promise<GenerateKeyResult> {\n // Generate a random key: sk_live_<32 random bytes as hex>\n const randomPart = randomBytes(32).toString('hex');\n const key = `sk_live_${randomPart}`;\n const keyPrefix = key.substring(0, 16);\n const keyHash = ApiKey.hashKey(key);\n\n const apiKey = new ApiKey({\n db: options.db || profile.options?.db,\n profileId: profile.id as string,\n name: options.name,\n scopes: options.scopes || ['*'],\n expiresAt: options.expiresAt ?? null,\n });\n apiKey.keyHash = keyHash;\n apiKey.keyPrefix = keyPrefix;\n\n await apiKey.initialize();\n await apiKey.save();\n\n return { key, apiKey };\n }\n\n /**\n * Verify an API key and return the ApiKey record if valid\n */\n static async verify(\n key: string,\n options: SmrtObjectOptions = {},\n ): Promise<ApiKey | null> {\n const keyHash = ApiKey.hashKey(key);\n\n const { ApiKeyCollection } = await import(\n '../collections/ApiKeyCollection'\n );\n const collection = await ApiKeyCollection.create(options);\n\n const apiKey = await collection.findOne({\n where: { keyHash },\n });\n\n if (!apiKey) return null;\n if (!apiKey.isValid()) return null;\n\n // Record usage\n await apiKey.recordUsage();\n\n return apiKey;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAoCO,IAAM,SAAN,cAAqB,WAAW;CAKrC;;;;CAKA,UAAkB;;;;CAKlB,YAAoB;;;;CAKpB,OAAe;;;;CAKf,SAAmB,CAAC;;;;CAKpB,aAA0B;;;;CAK1B,YAAyB;;;;CAKzB,YAAyB;CAEzB,YAAY,UAAyB,CAAC,GAAG;EACvC,MAAM,OAAO;EACb,IAAI,QAAQ,WAAW,KAAK,YAAY,QAAQ;EAChD,IAAI,QAAQ,MAAM,KAAK,OAAO,QAAQ;EACtC,IAAI,QAAQ,QAAQ,KAAK,SAAS,QAAQ;EAC1C,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;CAChE;;;;CAKA,MAAM,aAAsC;EAC1C,OAAQ,MAAM,KAAK,WAAW,WAAW;CAC3C;;;;CAKA,UAAmB;EACjB,IAAI,KAAK,WAAW,OAAO;EAC3B,IAAI,KAAK,aAAa,KAAK,4BAAY,IAAI,KAAK,GAAG,OAAO;EAC1D,OAAO;CACT;;;;CAKA,SAAS,OAAwB;EAC/B,OAAO,KAAK,OAAO,SAAS,KAAK,KAAK,KAAK,OAAO,SAAS,GAAG;CAChE;;;;CAKA,MAAM,SAAwB;EAC5B,KAAK,4BAAY,IAAI,KAAK;EAC1B,MAAM,KAAK,KAAK;CAClB;;;;CAKA,MAAM,cAA6B;EACjC,KAAK,6BAAa,IAAI,KAAK;EAC3B,MAAM,KAAK,KAAK;CAClB;;;;CAKA,OAAO,QAAQ,KAAqB;EAClC,OAAO,WAAW,QAAQ,CAAA,CAAE,OAAO,GAAG,CAAA,CAAE,OAAO,KAAK;CACtD;;;;CAKA,aAAa,SACX,SACA,SAM4B;EAG5B,MAAM,MAAM,WADO,YAAY,EAAE,CAAA,CAAE,SAAS,KACrB;EACvB,MAAM,YAAY,IAAI,UAAU,GAAG,EAAE;EACrC,MAAM,UAAU,OAAO,QAAQ,GAAG;EAElC,MAAM,SAAS,IAAI,OAAO;GACxB,IAAI,QAAQ,MAAM,QAAQ,SAAS;GACnC,WAAW,QAAQ;GACnB,MAAM,QAAQ;GACd,QAAQ,QAAQ,UAAU,CAAC,GAAG;GAC9B,WAAW,QAAQ,aAAa;EAClC,CAAC;EACD,OAAO,UAAU;EACjB,OAAO,YAAY;EAEnB,MAAM,OAAO,WAAW;EACxB,MAAM,OAAO,KAAK;EAElB,OAAO;GAAE;GAAK;EAAO;CACvB;;;;CAKA,aAAa,OACX,KACA,UAA6B,CAAC,GACN;EACxB,MAAM,UAAU,OAAO,QAAQ,GAAG;EAElC,MAAM,EAAE,qBAAqB,MAAM,OACjC,iCAAA,CAAA,MAAA,MAAA,EAAA,CAAA;EAIF,MAAM,SAAS,OAAM,MAFI,iBAAiB,OAAO,OAAO,EAAA,CAExB,QAAQ,EACtC,OAAO,EAAE,QAAQ,EACnB,CAAC;EAED,IAAI,CAAC,QAAQ,OAAO;EACpB,IAAI,CAAC,OAAO,QAAQ,GAAG,OAAO;EAG9B,MAAM,OAAO,YAAY;EAEzB,OAAO;CACT;AACF;AAvJE,gBAAA,CADC,WAAW,WAAW,EAAE,UAAU,KAAK,CAAC,CAAA,GAJ9B,OAKX,WAAA,aAAA,CAAA;AALW,SAAN,gBAAA,CARN,KAAK;CACJ,WAAW;CACX,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAE;CAChC,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAE;CAGhC,KAAK;EAAE,SAAS;GAAC;GAAQ;GAAO;GAAU;EAAQ;EAAG,cAAc;CAAK;AAC1E,CAAC,CAAA,GACY,MAAA"}
1
+ {"version":3,"file":"ApiKey-BrNyaaZN.js","names":[],"sources":["../../src/models/ApiKey.ts"],"sourcesContent":["/**\n * ApiKey - API keys for CLI and CI authentication\n *\n * Provides secure API key management with hashing, scopes, and expiration.\n * Keys are stored as hashes - the plaintext is only returned on creation.\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 { Profile } from './Profile';\n\nexport interface ApiKeyOptions extends SmrtObjectOptions {\n profileId?: string;\n name?: string;\n scopes?: string[];\n expiresAt?: Date | null;\n}\n\nexport interface GenerateKeyResult {\n key: string;\n apiKey: ApiKey;\n}\n\n@smrt({\n tableName: 'api_keys',\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n // create/revoke are admin operations invoked in-process via the CLI; only\n // read-only list/get are exposed over HTTP.\n cli: { include: ['list', 'get', 'create', 'revoke'], skipApiCheck: true },\n})\nexport class ApiKey extends SmrtObject {\n /**\n * Link to the Profile (Person, Organization, Bot)\n */\n @foreignKey('Profile', { required: true })\n profileId?: string;\n\n /**\n * SHA-256 hash of the API key\n */\n keyHash: string = '';\n\n /**\n * First 8 characters of the key for identification (e.g., \"sk_live_a1b2...\")\n */\n keyPrefix: string = '';\n\n /**\n * Human-readable name for the key (e.g., \"CI Bot\", \"Local CLI\")\n */\n name: string = '';\n\n /**\n * Scopes/permissions for this key\n */\n scopes: string[] = [];\n\n /**\n * Last time this key was used\n */\n lastUsedAt: Date | null = null;\n\n /**\n * When this key expires (null = never)\n */\n expiresAt: Date | null = null;\n\n /**\n * When this key was revoked (null = active)\n */\n revokedAt: Date | null = null;\n\n constructor(options: ApiKeyOptions = {}) {\n super(options);\n if (options.profileId) this.profileId = options.profileId;\n if (options.name) this.name = options.name;\n if (options.scopes) this.scopes = options.scopes;\n if (options.expiresAt !== undefined) this.expiresAt = options.expiresAt;\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 * Check if this key is valid (not expired, not revoked)\n */\n isValid(): boolean {\n if (this.revokedAt) return false;\n if (this.expiresAt && this.expiresAt < new Date()) return false;\n return true;\n }\n\n /**\n * Check if this key has a specific scope\n */\n hasScope(scope: string): boolean {\n return this.scopes.includes(scope) || this.scopes.includes('*');\n }\n\n /**\n * Revoke this key\n */\n async revoke(): Promise<void> {\n this.revokedAt = new Date();\n await this.save();\n }\n\n /**\n * Record usage of this key\n */\n async recordUsage(): Promise<void> {\n this.lastUsedAt = new Date();\n await this.save();\n }\n\n /**\n * Hash an API key\n */\n static hashKey(key: string): string {\n return createHash('sha256').update(key).digest('hex');\n }\n\n /**\n * Generate a new API key for a profile\n */\n static async generate(\n profile: Profile,\n options: {\n name: string;\n scopes?: string[];\n expiresAt?: Date | null;\n db?: SmrtObjectOptions['db'];\n },\n ): Promise<GenerateKeyResult> {\n // Generate a random key: sk_live_<32 random bytes as hex>\n const randomPart = randomBytes(32).toString('hex');\n const key = `sk_live_${randomPart}`;\n const keyPrefix = key.substring(0, 16);\n const keyHash = ApiKey.hashKey(key);\n\n const apiKey = new ApiKey({\n db: options.db || profile.options?.db,\n profileId: profile.id as string,\n name: options.name,\n scopes: options.scopes || ['*'],\n expiresAt: options.expiresAt ?? null,\n });\n apiKey.keyHash = keyHash;\n apiKey.keyPrefix = keyPrefix;\n\n await apiKey.initialize();\n await apiKey.save();\n\n return { key, apiKey };\n }\n\n /**\n * Verify an API key and return the ApiKey record if valid\n */\n static async verify(\n key: string,\n options: SmrtObjectOptions = {},\n ): Promise<ApiKey | null> {\n const keyHash = ApiKey.hashKey(key);\n\n const { ApiKeyCollection } = await import(\n '../collections/ApiKeyCollection'\n );\n const collection = await ApiKeyCollection.create(options);\n\n const apiKey = await collection.findOne({\n where: { keyHash },\n });\n\n if (!apiKey) return null;\n if (!apiKey.isValid()) return null;\n\n // Record usage\n await apiKey.recordUsage();\n\n return apiKey;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAoCO,IAAM,SAAN,cAAqB,WAAW;CAKrC;;;;CAKA,UAAkB;;;;CAKlB,YAAoB;;;;CAKpB,OAAe;;;;CAKf,SAAmB,CAAC;;;;CAKpB,aAA0B;;;;CAK1B,YAAyB;;;;CAKzB,YAAyB;CAEzB,YAAY,UAAyB,CAAC,GAAG;EACvC,MAAM,OAAO;EACb,IAAI,QAAQ,WAAW,KAAK,YAAY,QAAQ;EAChD,IAAI,QAAQ,MAAM,KAAK,OAAO,QAAQ;EACtC,IAAI,QAAQ,QAAQ,KAAK,SAAS,QAAQ;EAC1C,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;CAChE;;;;CAKA,MAAM,aAAsC;EAC1C,OAAQ,MAAM,KAAK,WAAW,WAAW;CAC3C;;;;CAKA,UAAmB;EACjB,IAAI,KAAK,WAAW,OAAO;EAC3B,IAAI,KAAK,aAAa,KAAK,4BAAY,IAAI,KAAK,GAAG,OAAO;EAC1D,OAAO;CACT;;;;CAKA,SAAS,OAAwB;EAC/B,OAAO,KAAK,OAAO,SAAS,KAAK,KAAK,KAAK,OAAO,SAAS,GAAG;CAChE;;;;CAKA,MAAM,SAAwB;EAC5B,KAAK,4BAAY,IAAI,KAAK;EAC1B,MAAM,KAAK,KAAK;CAClB;;;;CAKA,MAAM,cAA6B;EACjC,KAAK,6BAAa,IAAI,KAAK;EAC3B,MAAM,KAAK,KAAK;CAClB;;;;CAKA,OAAO,QAAQ,KAAqB;EAClC,OAAO,WAAW,QAAQ,CAAA,CAAE,OAAO,GAAG,CAAA,CAAE,OAAO,KAAK;CACtD;;;;CAKA,aAAa,SACX,SACA,SAM4B;EAG5B,MAAM,MAAM,WADO,YAAY,EAAE,CAAA,CAAE,SAAS,KACrB;EACvB,MAAM,YAAY,IAAI,UAAU,GAAG,EAAE;EACrC,MAAM,UAAU,OAAO,QAAQ,GAAG;EAElC,MAAM,SAAS,IAAI,OAAO;GACxB,IAAI,QAAQ,MAAM,QAAQ,SAAS;GACnC,WAAW,QAAQ;GACnB,MAAM,QAAQ;GACd,QAAQ,QAAQ,UAAU,CAAC,GAAG;GAC9B,WAAW,QAAQ,aAAa;EAClC,CAAC;EACD,OAAO,UAAU;EACjB,OAAO,YAAY;EAEnB,MAAM,OAAO,WAAW;EACxB,MAAM,OAAO,KAAK;EAElB,OAAO;GAAE;GAAK;EAAO;CACvB;;;;CAKA,aAAa,OACX,KACA,UAA6B,CAAC,GACN;EACxB,MAAM,UAAU,OAAO,QAAQ,GAAG;EAElC,MAAM,EAAE,qBAAqB,MAAM,OACjC,iCAAA,CAAA,MAAA,MAAA,EAAA,CAAA;EAIF,MAAM,SAAS,OAAM,MAFI,iBAAiB,OAAO,OAAO,EAAA,CAExB,QAAQ,EACtC,OAAO,EAAE,QAAQ,EACnB,CAAC;EAED,IAAI,CAAC,QAAQ,OAAO;EACpB,IAAI,CAAC,OAAO,QAAQ,GAAG,OAAO;EAG9B,MAAM,OAAO,YAAY;EAEzB,OAAO;CACT;AACF;AAvJE,gBAAA,CADC,WAAW,WAAW,EAAE,UAAU,KAAK,CAAC,CAAA,GAJ9B,OAKX,WAAA,aAAA,CAAA;AALW,SAAN,gBAAA,CARN,KAAK;CACJ,WAAW;CACX,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAE;CAChC,KAAK,EAAE,SAAS,CAAC,QAAQ,KAAK,EAAE;CAGhC,KAAK;EAAE,SAAS;GAAC;GAAQ;GAAO;GAAU;EAAQ;EAAG,cAAc;CAAK;AAC1E,CAAC,CAAA,GACY,MAAA"}
@@ -1,5 +1,5 @@
1
1
  import { x as __exportAll } from "./NostrIdentityCollection-CG6HGf6c.js";
2
- import { t as ApiKey } from "./ApiKey-3DlNEc91.js";
2
+ import { t as ApiKey } from "./ApiKey-BrNyaaZN.js";
3
3
  import { SmrtCollection } from "@happyvertical/smrt-core";
4
4
  //#region src/collections/ApiKeyCollection.ts
5
5
  var ApiKeyCollection_exports = /* @__PURE__ */ __exportAll({ ApiKeyCollection: () => ApiKeyCollection });
@@ -56,7 +56,7 @@ var ApiKeyCollection = class extends SmrtCollection {
56
56
  */
57
57
  async revokeByPrefix(keyPrefix) {
58
58
  const key = await this.findOne({ where: { keyPrefix } });
59
- if (key && key.isValid()) {
59
+ if (key?.isValid()) {
60
60
  await key.revoke();
61
61
  return true;
62
62
  }
@@ -80,4 +80,4 @@ var ApiKeyCollection = class extends SmrtCollection {
80
80
  //#endregion
81
81
  export { ApiKeyCollection_exports as n, ApiKeyCollection as t };
82
82
 
83
- //# sourceMappingURL=ApiKeyCollection-PwVs0qDb.js.map
83
+ //# sourceMappingURL=ApiKeyCollection-DNhDkS7O.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ApiKeyCollection-PwVs0qDb.js","names":[],"sources":["../../src/collections/ApiKeyCollection.ts"],"sourcesContent":["/**\n * ApiKeyCollection - Collection for managing API keys\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { ApiKey, type GenerateKeyResult } from '../models/ApiKey';\nimport type { Profile } from '../models/Profile';\n\nexport class ApiKeyCollection extends SmrtCollection<ApiKey> {\n static readonly _itemClass = ApiKey;\n\n /**\n * Find all keys for a profile\n */\n async findByProfile(profileId: string): Promise<ApiKey[]> {\n return await this.list({\n where: { profileId },\n });\n }\n\n /**\n * Find active (non-revoked, non-expired) keys for a profile\n */\n async findActiveByProfile(profileId: string): Promise<ApiKey[]> {\n const keys = await this.findByProfile(profileId);\n return keys.filter((key) => key.isValid());\n }\n\n /**\n * Find key by hash\n */\n async findByHash(keyHash: string): Promise<ApiKey | null> {\n return await this.findOne({\n where: { keyHash },\n });\n }\n\n /**\n * Verify an API key string and return the record if valid\n */\n async verify(key: string): Promise<ApiKey | null> {\n const keyHash = ApiKey.hashKey(key);\n const apiKey = await this.findByHash(keyHash);\n\n if (!apiKey) return null;\n if (!apiKey.isValid()) return null;\n\n // Record usage\n await apiKey.recordUsage();\n\n return apiKey;\n }\n\n /**\n * Generate a new key for a profile\n */\n async generateForProfile(\n profile: Profile,\n options: {\n name: string;\n scopes?: string[];\n expiresAt?: Date | null;\n },\n ): Promise<GenerateKeyResult> {\n return await ApiKey.generate(profile, {\n ...options,\n db: this.options.db,\n });\n }\n\n /**\n * Revoke all keys for a profile\n */\n async revokeAllForProfile(profileId: string): Promise<number> {\n const keys = await this.findActiveByProfile(profileId);\n for (const key of keys) {\n await key.revoke();\n }\n return keys.length;\n }\n\n /**\n * Revoke a specific key by prefix\n */\n async revokeByPrefix(keyPrefix: string): Promise<boolean> {\n const key = await this.findOne({\n where: { keyPrefix },\n });\n\n if (key && key.isValid()) {\n await key.revoke();\n return true;\n }\n return false;\n }\n\n /**\n * Clean up expired keys (soft delete by setting revokedAt)\n */\n async cleanupExpired(): Promise<number> {\n const now = new Date();\n const allKeys = await this.list({});\n let cleaned = 0;\n\n for (const key of allKeys) {\n if (key.expiresAt && key.expiresAt < now && !key.revokedAt) {\n key.revokedAt = now;\n await key.save();\n cleaned++;\n }\n }\n\n return cleaned;\n }\n}\n"],"mappings":";;;;;AAQO,IAAM,mBAAN,cAA+B,eAAuB;CAC3D,OAAgB,aAAa;;;;CAK7B,MAAM,cAAc,WAAsC;EACxD,OAAO,MAAM,KAAK,KAAK,EACrB,OAAO,EAAE,UAAU,EACrB,CAAC;CACH;;;;CAKA,MAAM,oBAAoB,WAAsC;EAE9D,QAAO,MADY,KAAK,cAAc,SAAS,EAAA,CACnC,QAAQ,QAAQ,IAAI,QAAQ,CAAC;CAC3C;;;;CAKA,MAAM,WAAW,SAAyC;EACxD,OAAO,MAAM,KAAK,QAAQ,EACxB,OAAO,EAAE,QAAQ,EACnB,CAAC;CACH;;;;CAKA,MAAM,OAAO,KAAqC;EAChD,MAAM,UAAU,OAAO,QAAQ,GAAG;EAClC,MAAM,SAAS,MAAM,KAAK,WAAW,OAAO;EAE5C,IAAI,CAAC,QAAQ,OAAO;EACpB,IAAI,CAAC,OAAO,QAAQ,GAAG,OAAO;EAG9B,MAAM,OAAO,YAAY;EAEzB,OAAO;CACT;;;;CAKA,MAAM,mBACJ,SACA,SAK4B;EAC5B,OAAO,MAAM,OAAO,SAAS,SAAS;GACpC,GAAG;GACH,IAAI,KAAK,QAAQ;EACnB,CAAC;CACH;;;;CAKA,MAAM,oBAAoB,WAAoC;EAC5D,MAAM,OAAO,MAAM,KAAK,oBAAoB,SAAS;EACrD,KAAA,MAAW,OAAO,MAChB,MAAM,IAAI,OAAO;EAEnB,OAAO,KAAK;CACd;;;;CAKA,MAAM,eAAe,WAAqC;EACxD,MAAM,MAAM,MAAM,KAAK,QAAQ,EAC7B,OAAO,EAAE,UAAU,EACrB,CAAC;EAED,IAAI,OAAO,IAAI,QAAQ,GAAG;GACxB,MAAM,IAAI,OAAO;GACjB,OAAO;EACT;EACA,OAAO;CACT;;;;CAKA,MAAM,iBAAkC;EACtC,MAAM,sBAAM,IAAI,KAAK;EACrB,MAAM,UAAU,MAAM,KAAK,KAAK,CAAC,CAAC;EAClC,IAAI,UAAU;EAEd,KAAA,MAAW,OAAO,SAChB,IAAI,IAAI,aAAa,IAAI,YAAY,OAAO,CAAC,IAAI,WAAW;GAC1D,IAAI,YAAY;GAChB,MAAM,IAAI,KAAK;GACf;EACF;EAGF,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"ApiKeyCollection-DNhDkS7O.js","names":[],"sources":["../../src/collections/ApiKeyCollection.ts"],"sourcesContent":["/**\n * ApiKeyCollection - Collection for managing API keys\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { ApiKey, type GenerateKeyResult } from '../models/ApiKey';\nimport type { Profile } from '../models/Profile';\n\nexport class ApiKeyCollection extends SmrtCollection<ApiKey> {\n static readonly _itemClass = ApiKey;\n\n /**\n * Find all keys for a profile\n */\n async findByProfile(profileId: string): Promise<ApiKey[]> {\n return await this.list({\n where: { profileId },\n });\n }\n\n /**\n * Find active (non-revoked, non-expired) keys for a profile\n */\n async findActiveByProfile(profileId: string): Promise<ApiKey[]> {\n const keys = await this.findByProfile(profileId);\n return keys.filter((key) => key.isValid());\n }\n\n /**\n * Find key by hash\n */\n async findByHash(keyHash: string): Promise<ApiKey | null> {\n return await this.findOne({\n where: { keyHash },\n });\n }\n\n /**\n * Verify an API key string and return the record if valid\n */\n async verify(key: string): Promise<ApiKey | null> {\n const keyHash = ApiKey.hashKey(key);\n const apiKey = await this.findByHash(keyHash);\n\n if (!apiKey) return null;\n if (!apiKey.isValid()) return null;\n\n // Record usage\n await apiKey.recordUsage();\n\n return apiKey;\n }\n\n /**\n * Generate a new key for a profile\n */\n async generateForProfile(\n profile: Profile,\n options: {\n name: string;\n scopes?: string[];\n expiresAt?: Date | null;\n },\n ): Promise<GenerateKeyResult> {\n return await ApiKey.generate(profile, {\n ...options,\n db: this.options.db,\n });\n }\n\n /**\n * Revoke all keys for a profile\n */\n async revokeAllForProfile(profileId: string): Promise<number> {\n const keys = await this.findActiveByProfile(profileId);\n for (const key of keys) {\n await key.revoke();\n }\n return keys.length;\n }\n\n /**\n * Revoke a specific key by prefix\n */\n async revokeByPrefix(keyPrefix: string): Promise<boolean> {\n const key = await this.findOne({\n where: { keyPrefix },\n });\n\n if (key?.isValid()) {\n await key.revoke();\n return true;\n }\n return false;\n }\n\n /**\n * Clean up expired keys (soft delete by setting revokedAt)\n */\n async cleanupExpired(): Promise<number> {\n const now = new Date();\n const allKeys = await this.list({});\n let cleaned = 0;\n\n for (const key of allKeys) {\n if (key.expiresAt && key.expiresAt < now && !key.revokedAt) {\n key.revokedAt = now;\n await key.save();\n cleaned++;\n }\n }\n\n return cleaned;\n }\n}\n"],"mappings":";;;;;AAQO,IAAM,mBAAN,cAA+B,eAAuB;CAC3D,OAAgB,aAAa;;;;CAK7B,MAAM,cAAc,WAAsC;EACxD,OAAO,MAAM,KAAK,KAAK,EACrB,OAAO,EAAE,UAAU,EACrB,CAAC;CACH;;;;CAKA,MAAM,oBAAoB,WAAsC;EAE9D,QAAO,MADY,KAAK,cAAc,SAAS,EAAA,CACnC,QAAQ,QAAQ,IAAI,QAAQ,CAAC;CAC3C;;;;CAKA,MAAM,WAAW,SAAyC;EACxD,OAAO,MAAM,KAAK,QAAQ,EACxB,OAAO,EAAE,QAAQ,EACnB,CAAC;CACH;;;;CAKA,MAAM,OAAO,KAAqC;EAChD,MAAM,UAAU,OAAO,QAAQ,GAAG;EAClC,MAAM,SAAS,MAAM,KAAK,WAAW,OAAO;EAE5C,IAAI,CAAC,QAAQ,OAAO;EACpB,IAAI,CAAC,OAAO,QAAQ,GAAG,OAAO;EAG9B,MAAM,OAAO,YAAY;EAEzB,OAAO;CACT;;;;CAKA,MAAM,mBACJ,SACA,SAK4B;EAC5B,OAAO,MAAM,OAAO,SAAS,SAAS;GACpC,GAAG;GACH,IAAI,KAAK,QAAQ;EACnB,CAAC;CACH;;;;CAKA,MAAM,oBAAoB,WAAoC;EAC5D,MAAM,OAAO,MAAM,KAAK,oBAAoB,SAAS;EACrD,KAAA,MAAW,OAAO,MAChB,MAAM,IAAI,OAAO;EAEnB,OAAO,KAAK;CACd;;;;CAKA,MAAM,eAAe,WAAqC;EACxD,MAAM,MAAM,MAAM,KAAK,QAAQ,EAC7B,OAAO,EAAE,UAAU,EACrB,CAAC;EAED,IAAI,KAAK,QAAQ,GAAG;GAClB,MAAM,IAAI,OAAO;GACjB,OAAO;EACT;EACA,OAAO;CACT;;;;CAKA,MAAM,iBAAkC;EACtC,MAAM,sBAAM,IAAI,KAAK;EACrB,MAAM,UAAU,MAAM,KAAK,KAAK,CAAC,CAAC;EAClC,IAAI,UAAU;EAEd,KAAA,MAAW,OAAO,SAChB,IAAI,IAAI,aAAa,IAAI,YAAY,OAAO,CAAC,IAAI,WAAW;GAC1D,IAAI,YAAY;GAChB,MAAM,IAAI,KAAK;GACf;EACF;EAGF,OAAO;CACT;AACF"}
@@ -66,7 +66,7 @@ var ProfileAssetCollection = class extends SmrtJunction {
66
66
  profileCollectionPromise = null;
67
67
  async getProfileCollection() {
68
68
  if (!this.profileCollectionPromise) {
69
- const { ProfileCollection } = await import("./ProfileCollection-DlhdOnDr.js").then((n) => n.n);
69
+ const { ProfileCollection } = await import("./ProfileCollection-BOMOloTL.js").then((n) => n.n);
70
70
  this.profileCollectionPromise = ProfileCollection.create({ db: this.db });
71
71
  }
72
72
  return this.profileCollectionPromise;
@@ -90,4 +90,4 @@ ProfileAssetCollection = __decorateClass([smrt({
90
90
  //#endregion
91
91
  export { ProfileAssetCollection_exports as n, ProfileAsset as r, ProfileAssetCollection as t };
92
92
 
93
- //# sourceMappingURL=ProfileAssetCollection-BY2Wbem3.js.map
93
+ //# sourceMappingURL=ProfileAssetCollection-DzUwbtTq.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProfileAssetCollection-BY2Wbem3.js","names":[],"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"],"mappings":";;;;;;;;;;;;;AA0BO,IAAM,eAAN,cAA2B,WAAW;CAE3C,WAA0B;CAG1B,YAAY;CAGZ,UAAU;CAGV,eAAe;CAGf,YAAY;CAEZ,YAAY,UAA+B,CAAC,GAAG;EAC7C,MAAM,OAAO;EACb,IAAI,QAAQ,WAAW,KAAK,YAAY,QAAQ;EAChD,IAAI,QAAQ,SAAS,KAAK,UAAU,QAAQ;EAC5C,IAAI,QAAQ,cAAc,KAAK,eAAe,QAAQ;EACtD,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;EAC9D,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;CAC9D;AACF;AAtBE,kBAAA,CADC,SAAS,EAAE,UAAU,KAAK,CAAC,CAAA,GADjB,aAEX,WAAA,YAAA,CAAA;AAGA,kBAAA,CADC,WAAW,WAAW,EAAE,UAAU,KAAK,CAAC,CAAA,GAJ9B,aAKX,WAAA,aAAA,CAAA;AAGA,kBAAA,CADC,gBAAgB,oCAAoC,EAAE,UAAU,KAAK,CAAC,CAAA,GAP5D,aAQX,WAAA,WAAA,CAAA;AAGA,kBAAA,CADC,MAAM,EAAE,UAAU,KAAK,CAAC,CAAA,GAVd,aAWX,WAAA,gBAAA,CAAA;AAGA,kBAAA,CADC,MAAM,CAAA,GAbI,aAcX,WAAA,aAAA,CAAA;AAdW,eAAN,kBAAA,CARN,aAAa,EAAE,MAAM,WAAW,CAAC,GACjC,KAAK;CACJ,WAAW;CACX,iBAAiB;EAAC;EAAc;EAAY;CAAc;CAC1D,KAAK;CACL,KAAK;CACL,KAAK;AACP,CAAC,CAAA,GACY,YAAA;;;;;;;;;;;;;;;;;;;ACRN,IAAM,yBAAN,cAAqC,aAA2B;CAE3D,YAAY;CACZ,aAAa;CAEf,2BAA8D;CAEtE,MAAc,uBAAmD;EAC/D,IAAI,CAAC,KAAK,0BAA0B;GAClC,MAAM,EAAE,sBAAsB,MAAM,OAAO,kCAAA,CAAA,MAAA,MAAA,EAAA,CAAA;GAC3C,KAAK,2BAA2B,kBAAkB,OAAO,EAAE,IAAI,KAAK,GAAG,CAAC;EAC1E;EAEA,OAAO,KAAK;CACd;CAEA,MAAM,UAAU,WAAmB,cAAyC;EAC1E,OAAO,6BACL,MAAM,KAAK,qBAAqB,GAChC,WACA,YACF;CACF;CAEA,MAAM,SACJ,WACA,OACA,eAAe,cACf,YAAY,GACG;EACf,MAAM,4BACJ,MAAM,KAAK,qBAAqB,GAChC,WACA,WACA,OACA,cACA,SACF;CACF;CAEA,MAAM,YACJ,WACA,SACA,cACe;EACf,MAAM,+BACJ,MAAM,KAAK,qBAAqB,GAChC,WACA,WACA,SACA,YACF;CACF;AACF;AApDE,cADW,wBACK,cAAa,YAAA;AADlB,yBAAN,gBAAA,CALN,KAAK;CACJ,KAAK;CACL,KAAK;CACL,KAAK;AACP,CAAC,CAAA,GACY,sBAAA"}
1
+ {"version":3,"file":"ProfileAssetCollection-DzUwbtTq.js","names":[],"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"],"mappings":";;;;;;;;;;;;;AA0BO,IAAM,eAAN,cAA2B,WAAW;CAE3C,WAA0B;CAG1B,YAAY;CAGZ,UAAU;CAGV,eAAe;CAGf,YAAY;CAEZ,YAAY,UAA+B,CAAC,GAAG;EAC7C,MAAM,OAAO;EACb,IAAI,QAAQ,WAAW,KAAK,YAAY,QAAQ;EAChD,IAAI,QAAQ,SAAS,KAAK,UAAU,QAAQ;EAC5C,IAAI,QAAQ,cAAc,KAAK,eAAe,QAAQ;EACtD,IAAI,QAAQ,cAAc,KAAA,GAAW,KAAK,YAAY,QAAQ;EAC9D,IAAI,QAAQ,aAAa,KAAA,GAAW,KAAK,WAAW,QAAQ;CAC9D;AACF;AAtBE,kBAAA,CADC,SAAS,EAAE,UAAU,KAAK,CAAC,CAAA,GADjB,aAEX,WAAA,YAAA,CAAA;AAGA,kBAAA,CADC,WAAW,WAAW,EAAE,UAAU,KAAK,CAAC,CAAA,GAJ9B,aAKX,WAAA,aAAA,CAAA;AAGA,kBAAA,CADC,gBAAgB,oCAAoC,EAAE,UAAU,KAAK,CAAC,CAAA,GAP5D,aAQX,WAAA,WAAA,CAAA;AAGA,kBAAA,CADC,MAAM,EAAE,UAAU,KAAK,CAAC,CAAA,GAVd,aAWX,WAAA,gBAAA,CAAA;AAGA,kBAAA,CADC,MAAM,CAAA,GAbI,aAcX,WAAA,aAAA,CAAA;AAdW,eAAN,kBAAA,CARN,aAAa,EAAE,MAAM,WAAW,CAAC,GACjC,KAAK;CACJ,WAAW;CACX,iBAAiB;EAAC;EAAc;EAAY;CAAc;CAC1D,KAAK;CACL,KAAK;CACL,KAAK;AACP,CAAC,CAAA,GACY,YAAA;;;;;;;;;;;;;;;;;;;ACRN,IAAM,yBAAN,cAAqC,aAA2B;CAE3D,YAAY;CACZ,aAAa;CAEf,2BAA8D;CAEtE,MAAc,uBAAmD;EAC/D,IAAI,CAAC,KAAK,0BAA0B;GAClC,MAAM,EAAE,sBAAsB,MAAM,OAAO,kCAAA,CAAA,MAAA,MAAA,EAAA,CAAA;GAC3C,KAAK,2BAA2B,kBAAkB,OAAO,EAAE,IAAI,KAAK,GAAG,CAAC;EAC1E;EAEA,OAAO,KAAK;CACd;CAEA,MAAM,UAAU,WAAmB,cAAyC;EAC1E,OAAO,6BACL,MAAM,KAAK,qBAAqB,GAChC,WACA,YACF;CACF;CAEA,MAAM,SACJ,WACA,OACA,eAAe,cACf,YAAY,GACG;EACf,MAAM,4BACJ,MAAM,KAAK,qBAAqB,GAChC,WACA,WACA,OACA,cACA,SACF;CACF;CAEA,MAAM,YACJ,WACA,SACA,cACe;EACf,MAAM,+BACJ,MAAM,KAAK,qBAAqB,GAChC,WACA,WACA,SACA,YACF;CACF;AACF;AApDE,cADW,wBACK,cAAa,YAAA;AADlB,yBAAN,gBAAA,CALN,KAAK;CACJ,KAAK;CACL,KAAK;CACL,KAAK;AACP,CAAC,CAAA,GACY,sBAAA"}
@@ -185,7 +185,7 @@ var Profile = class extends SmrtObject {
185
185
  if (existing.length > 0) await existing[0].delete();
186
186
  }
187
187
  async getProfileAssetCollection() {
188
- const { ProfileAssetCollection } = await import("./ProfileAssetCollection-BY2Wbem3.js").then((n) => n.n);
188
+ const { ProfileAssetCollection } = await import("./ProfileAssetCollection-DzUwbtTq.js").then((n) => n.n);
189
189
  return ProfileAssetCollection.create({ db: this.db });
190
190
  }
191
191
  async getAssets(relationship) {
@@ -379,7 +379,7 @@ var Profile = class extends SmrtObject {
379
379
  * @returns Array of API keys
380
380
  */
381
381
  async getApiKeys() {
382
- const { ApiKeyCollection } = await import("./ApiKeyCollection-PwVs0qDb.js").then((n) => n.n);
382
+ const { ApiKeyCollection } = await import("./ApiKeyCollection-DNhDkS7O.js").then((n) => n.n);
383
383
  return await (await ApiKeyCollection.create(this.options)).findByProfile(this.id);
384
384
  }
385
385
  /**
@@ -388,7 +388,7 @@ var Profile = class extends SmrtObject {
388
388
  * @returns Array of active API keys
389
389
  */
390
390
  async getActiveApiKeys() {
391
- const { ApiKeyCollection } = await import("./ApiKeyCollection-PwVs0qDb.js").then((n) => n.n);
391
+ const { ApiKeyCollection } = await import("./ApiKeyCollection-DNhDkS7O.js").then((n) => n.n);
392
392
  return await (await ApiKeyCollection.create(this.options)).findActiveByProfile(this.id);
393
393
  }
394
394
  /**
@@ -398,7 +398,7 @@ var Profile = class extends SmrtObject {
398
398
  * @returns The generated key (plaintext) and ApiKey record
399
399
  */
400
400
  async generateApiKey(options) {
401
- const { ApiKey } = await import("./ApiKey-3DlNEc91.js").then((n) => n.n);
401
+ const { ApiKey } = await import("./ApiKey-BrNyaaZN.js").then((n) => n.n);
402
402
  return await ApiKey.generate(this, {
403
403
  ...options,
404
404
  db: this.options?.db
@@ -638,4 +638,4 @@ var ProfileCollection = class extends SmrtCollection {
638
638
  //#endregion
639
639
  export { ProfileType_exports as a, ProfileType as i, ProfileCollection_exports as n, promptMessageOptions as o, Profile as r, smrtProfilesGenerateBioPrompt as s, ProfileCollection as t };
640
640
 
641
- //# sourceMappingURL=ProfileCollection-DlhdOnDr.js.map
641
+ //# sourceMappingURL=ProfileCollection-BOMOloTL.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProfileCollection-DlhdOnDr.js","names":[],"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 { ApiKey, GenerateKeyResult } from './ApiKey';\nimport type { AuditLog } from './AuditLog';\nimport type { NostrIdentity } from './NostrIdentity';\nimport type { OidcIdentity } from './OidcIdentity';\nimport type { ProfileMetadata } from './ProfileMetadata';\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: ProfileMetadata[] = [];\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: ProfileRelationship[] = [];\n\n @oneToMany('ProfileRelationship', { foreignKey: 'toProfileId' })\n relationshipsTo: ProfileRelationship[] = [];\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: unknown): 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 ProfileMetafieldCollection.create(\n this.options,\n );\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.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 if (!this.id || !metafield.id) {\n throw new Error(\n 'Profile.addMetadata requires a persisted profile and metafield (missing id)',\n );\n }\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, string>> {\n const { ProfileMetadataCollection } = await import(\n '../collections/ProfileMetadataCollection'\n );\n\n const metadataCollection = await ProfileMetadataCollection.create(\n this.options,\n );\n\n return await metadataCollection.getMetadataObject(this.id as string);\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, unknown>): 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 ProfileMetafieldCollection.create(\n this.options,\n );\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.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 =\n await ProfileRelationshipTypeCollection.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 ProfileRelationshipCollection.create(\n this.options,\n );\n\n const exists = await relationshipCollection.exists(\n this.id as string,\n toProfile.id as string,\n relationshipType.id as string,\n );\n\n if (!exists) {\n // Create the relationship\n const relationship = await relationshipCollection.create({\n fromProfileId: this.id ?? undefined,\n toProfileId: toProfile.id ?? undefined,\n typeId: relationshipType.id ?? undefined,\n contextProfileId: contextProfile?.id ?? undefined,\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 ProfileRelationshipCollection.create(\n this.options,\n );\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 =\n await ProfileRelationshipTypeCollection.create(this.options);\n\n const relationshipType = await relationshipTypeCollection.getBySlug(\n options.typeSlug,\n );\n typeId = relationshipType?.id ?? undefined;\n }\n\n // Fetch relationships based on direction\n if (direction === 'from') {\n return await relationshipCollection.getFromProfile(\n this.id as string,\n typeId,\n );\n } else if (direction === 'to') {\n return await relationshipCollection.getToProfile(\n this.id as string,\n typeId,\n );\n } else {\n return await relationshipCollection.getForProfile(\n this.id as string,\n typeId,\n );\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.create(this.options);\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 =\n await ProfileRelationshipTypeCollection.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 ProfileRelationshipCollection.create(\n this.options,\n );\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: unknown,\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<ApiKey[]> {\n const { ApiKeyCollection } = await import(\n '../collections/ApiKeyCollection'\n );\n const collection = await ApiKeyCollection.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<ApiKey[]> {\n const { ApiKeyCollection } = await import(\n '../collections/ApiKeyCollection'\n );\n const collection = await ApiKeyCollection.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<GenerateKeyResult> {\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<OidcIdentity[]> {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const collection = await OidcIdentityCollection.create(this.options);\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<OidcIdentity> {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const collection = await OidcIdentityCollection.create(this.options);\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<NostrIdentity[]> {\n const { NostrIdentityCollection } = await import(\n '../collections/NostrIdentityCollection'\n );\n const collection = await NostrIdentityCollection.create(this.options);\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<NostrIdentity> {\n const { NostrIdentityCollection } = await import(\n '../collections/NostrIdentityCollection'\n );\n const collection = await NostrIdentityCollection.create(this.options);\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<AuditLog[]> {\n const { AuditLogCollection } = await import(\n '../collections/AuditLogCollection'\n );\n const collection = await AuditLogCollection.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, unknown>;\n onBehalfOf?: Profile | null;\n }): Promise<AuditLog> {\n const { AuditLogCollection } = await import(\n '../collections/AuditLogCollection'\n );\n const collection = await AuditLogCollection.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, string>>> {\n const result = new Map<string, Record<string, string>>();\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, unknown> }>,\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"],"mappings":"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"}
1
+ {"version":3,"file":"ProfileCollection-BOMOloTL.js","names":[],"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 { ApiKey, GenerateKeyResult } from './ApiKey';\nimport type { AuditLog } from './AuditLog';\nimport type { NostrIdentity } from './NostrIdentity';\nimport type { OidcIdentity } from './OidcIdentity';\nimport type { ProfileMetadata } from './ProfileMetadata';\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: ProfileMetadata[] = [];\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: ProfileRelationship[] = [];\n\n @oneToMany('ProfileRelationship', { foreignKey: 'toProfileId' })\n relationshipsTo: ProfileRelationship[] = [];\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: unknown): 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 ProfileMetafieldCollection.create(\n this.options,\n );\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.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 if (!this.id || !metafield.id) {\n throw new Error(\n 'Profile.addMetadata requires a persisted profile and metafield (missing id)',\n );\n }\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, string>> {\n const { ProfileMetadataCollection } = await import(\n '../collections/ProfileMetadataCollection'\n );\n\n const metadataCollection = await ProfileMetadataCollection.create(\n this.options,\n );\n\n return await metadataCollection.getMetadataObject(this.id as string);\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, unknown>): 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 ProfileMetafieldCollection.create(\n this.options,\n );\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.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 =\n await ProfileRelationshipTypeCollection.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 ProfileRelationshipCollection.create(\n this.options,\n );\n\n const exists = await relationshipCollection.exists(\n this.id as string,\n toProfile.id as string,\n relationshipType.id as string,\n );\n\n if (!exists) {\n // Create the relationship\n const relationship = await relationshipCollection.create({\n fromProfileId: this.id ?? undefined,\n toProfileId: toProfile.id ?? undefined,\n typeId: relationshipType.id ?? undefined,\n contextProfileId: contextProfile?.id ?? undefined,\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 ProfileRelationshipCollection.create(\n this.options,\n );\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 =\n await ProfileRelationshipTypeCollection.create(this.options);\n\n const relationshipType = await relationshipTypeCollection.getBySlug(\n options.typeSlug,\n );\n typeId = relationshipType?.id ?? undefined;\n }\n\n // Fetch relationships based on direction\n if (direction === 'from') {\n return await relationshipCollection.getFromProfile(\n this.id as string,\n typeId,\n );\n } else if (direction === 'to') {\n return await relationshipCollection.getToProfile(\n this.id as string,\n typeId,\n );\n } else {\n return await relationshipCollection.getForProfile(\n this.id as string,\n typeId,\n );\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.create(this.options);\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 =\n await ProfileRelationshipTypeCollection.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 ProfileRelationshipCollection.create(\n this.options,\n );\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: unknown,\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<ApiKey[]> {\n const { ApiKeyCollection } = await import(\n '../collections/ApiKeyCollection'\n );\n const collection = await ApiKeyCollection.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<ApiKey[]> {\n const { ApiKeyCollection } = await import(\n '../collections/ApiKeyCollection'\n );\n const collection = await ApiKeyCollection.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<GenerateKeyResult> {\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<OidcIdentity[]> {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const collection = await OidcIdentityCollection.create(this.options);\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<OidcIdentity> {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const collection = await OidcIdentityCollection.create(this.options);\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<NostrIdentity[]> {\n const { NostrIdentityCollection } = await import(\n '../collections/NostrIdentityCollection'\n );\n const collection = await NostrIdentityCollection.create(this.options);\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<NostrIdentity> {\n const { NostrIdentityCollection } = await import(\n '../collections/NostrIdentityCollection'\n );\n const collection = await NostrIdentityCollection.create(this.options);\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<AuditLog[]> {\n const { AuditLogCollection } = await import(\n '../collections/AuditLogCollection'\n );\n const collection = await AuditLogCollection.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, unknown>;\n onBehalfOf?: Profile | null;\n }): Promise<AuditLog> {\n const { AuditLogCollection } = await import(\n '../collections/AuditLogCollection'\n );\n const collection = await AuditLogCollection.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, string>>> {\n const result = new Map<string, Record<string, string>>();\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, unknown> }>,\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"],"mappings":"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"}