@happyvertical/smrt-profiles 0.39.13 → 0.39.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/AGENTS.md +69 -5
  2. package/README.md +101 -50
  3. package/dist/chunks/{ApiKey-BrNyaaZN.js → ApiKey-Br9NGBnr.js} +3 -3
  4. package/dist/chunks/{ApiKey-BrNyaaZN.js.map → ApiKey-Br9NGBnr.js.map} +1 -1
  5. package/dist/chunks/{ApiKeyCollection-DNhDkS7O.js → ApiKeyCollection-CrTjdMz8.js} +3 -3
  6. package/dist/chunks/{ApiKeyCollection-DNhDkS7O.js.map → ApiKeyCollection-CrTjdMz8.js.map} +1 -1
  7. package/dist/chunks/{AuditLogCollection-CnIYU0aK.js → AuditLogCollection-BBIEI6BD.js} +2 -2
  8. package/dist/chunks/{AuditLogCollection-CnIYU0aK.js.map → AuditLogCollection-BBIEI6BD.js.map} +1 -1
  9. package/dist/chunks/{NostrIdentityCollection-CG6HGf6c.js → NostrIdentity-DF_a_WZk.js} +6 -125
  10. package/dist/chunks/NostrIdentity-DF_a_WZk.js.map +1 -0
  11. package/dist/chunks/NostrIdentityCollection-BRYUgt47.js +124 -0
  12. package/dist/chunks/NostrIdentityCollection-BRYUgt47.js.map +1 -0
  13. package/dist/chunks/OidcIdentity-ChkiT6hg.js +111 -0
  14. package/dist/chunks/OidcIdentity-ChkiT6hg.js.map +1 -0
  15. package/dist/chunks/OidcIdentityCollection-Ce4yv8p0.js +76 -0
  16. package/dist/chunks/OidcIdentityCollection-Ce4yv8p0.js.map +1 -0
  17. package/dist/chunks/{ProfileAssetCollection-DzUwbtTq.js → ProfileAssetCollection-CtlvXbGy.js} +3 -3
  18. package/dist/chunks/{ProfileAssetCollection-DzUwbtTq.js.map → ProfileAssetCollection-CtlvXbGy.js.map} +1 -1
  19. package/dist/chunks/ProfileCollection-Bix9yAiy.js +1161 -0
  20. package/dist/chunks/ProfileCollection-Bix9yAiy.js.map +1 -0
  21. package/dist/chunks/{ProfileMetadataCollection-CGcO4Iw_.js → ProfileMetadataCollection-loKVv_pj.js} +2 -2
  22. package/dist/chunks/{ProfileMetadataCollection-CGcO4Iw_.js.map → ProfileMetadataCollection-loKVv_pj.js.map} +1 -1
  23. package/dist/chunks/{ProfileMetafieldCollection-C8TX9HvN.js → ProfileMetafieldCollection-CACMY1hl.js} +2 -2
  24. package/dist/chunks/{ProfileMetafieldCollection-C8TX9HvN.js.map → ProfileMetafieldCollection-CACMY1hl.js.map} +1 -1
  25. package/dist/chunks/{ProfileRelationshipCollection-JnskbZTd.js → ProfileRelationshipCollection-CzQKfvMy.js} +5 -5
  26. package/dist/chunks/{ProfileRelationshipCollection-JnskbZTd.js.map → ProfileRelationshipCollection-CzQKfvMy.js.map} +1 -1
  27. package/dist/chunks/{ProfileRelationshipTermCollection-UopP7sja.js → ProfileRelationshipTermCollection-DzmU48P6.js} +2 -2
  28. package/dist/chunks/{ProfileRelationshipTermCollection-UopP7sja.js.map → ProfileRelationshipTermCollection-DzmU48P6.js.map} +1 -1
  29. package/dist/chunks/{ProfileRelationshipType-OMBn5cWR.js → ProfileRelationshipType-BjoaCt-u.js} +2 -2
  30. package/dist/chunks/{ProfileRelationshipType-OMBn5cWR.js.map → ProfileRelationshipType-BjoaCt-u.js.map} +1 -1
  31. package/dist/chunks/{ProfileRelationshipTypeCollection-CQu08kxF.js → ProfileRelationshipTypeCollection-Bh4xJ4-I.js} +3 -3
  32. package/dist/chunks/{ProfileRelationshipTypeCollection-CQu08kxF.js.map → ProfileRelationshipTypeCollection-Bh4xJ4-I.js.map} +1 -1
  33. package/dist/chunks/oidcProvisioningPrimitives-ZuVcDPt-.js +138 -0
  34. package/dist/chunks/oidcProvisioningPrimitives-ZuVcDPt-.js.map +1 -0
  35. package/dist/chunks/resolveIdentity-_h_2sGqJ.js +260 -0
  36. package/dist/chunks/resolveIdentity-_h_2sGqJ.js.map +1 -0
  37. package/dist/index.d.ts +127 -15
  38. package/dist/index.js +30 -273
  39. package/dist/index.js.map +1 -1
  40. package/dist/internal/oidc-provisioning.d.ts +17 -0
  41. package/dist/internal/oidc-provisioning.js +13 -0
  42. package/dist/internal/oidc-provisioning.js.map +1 -0
  43. package/dist/manifest.json +431 -46
  44. package/dist/oidc-provisioning.d.ts +83 -0
  45. package/dist/smrt-knowledge.json +105 -43
  46. package/dist/types.d.ts +26 -2
  47. package/dist/utils.d.ts +26 -2
  48. package/dist/utils.js +4 -4
  49. package/package.json +10 -6
  50. package/dist/chunks/NostrIdentityCollection-CG6HGf6c.js.map +0 -1
  51. package/dist/chunks/OidcIdentityCollection-DiLneB7J.js +0 -158
  52. package/dist/chunks/OidcIdentityCollection-DiLneB7J.js.map +0 -1
  53. package/dist/chunks/ProfileCollection-BOMOloTL.js +0 -641
  54. package/dist/chunks/ProfileCollection-BOMOloTL.js.map +0 -1
@@ -0,0 +1,124 @@
1
+ import { c as generateNostrKeypair, s as encryptPrivkey, t as NostrIdentity, y as __exportAll } from "./NostrIdentity-DF_a_WZk.js";
2
+ import { SmrtCollection } from "@happyvertical/smrt-core";
3
+ //#region src/collections/NostrIdentityCollection.ts
4
+ var NostrIdentityCollection_exports = /* @__PURE__ */ __exportAll({ NostrIdentityCollection: () => NostrIdentityCollection });
5
+ var NostrIdentityCollection = class extends SmrtCollection {
6
+ static _itemClass = NostrIdentity;
7
+ /**
8
+ * Find identities for a profile
9
+ */
10
+ async findByProfile(profileId) {
11
+ return await this.list({ where: { profileId } });
12
+ }
13
+ /**
14
+ * Find identity by public key
15
+ */
16
+ async findByPubkey(pubkey) {
17
+ return await this.findOne({ where: { pubkey } });
18
+ }
19
+ /**
20
+ * Find identity by email
21
+ */
22
+ async findByEmail(email) {
23
+ return await this.findOne({ where: { email: email.toLowerCase() } });
24
+ }
25
+ /**
26
+ * Find identity by NIP-05 username
27
+ */
28
+ async findByNip05(username) {
29
+ return await this.findOne({ where: { nip05Username: username.toLowerCase() } });
30
+ }
31
+ /**
32
+ * Link a new Nostr identity to a profile with a new keypair
33
+ * @param profile - The profile to link to
34
+ * @param email - Email address for this identity
35
+ * @param masterSecret - Server master secret for encryption
36
+ * @param nip05Username - Optional NIP-05 username (defaults to email local part)
37
+ */
38
+ async createForProfile(profile, email, masterSecret, nip05Username) {
39
+ const normalizedEmail = email.toLowerCase();
40
+ if (await this.findByEmail(normalizedEmail)) throw new Error(`Nostr identity already exists for email: ${email}`);
41
+ const keypair = generateNostrKeypair();
42
+ const encrypted = encryptPrivkey(keypair.privkey, masterSecret);
43
+ const username = nip05Username?.toLowerCase() || normalizedEmail.split("@")[0];
44
+ const identity = new NostrIdentity({
45
+ ...this.options,
46
+ profileId: profile.id,
47
+ pubkey: keypair.pubkey,
48
+ encryptedPrivkey: encrypted.ciphertext,
49
+ encryptionIv: encrypted.iv,
50
+ encryptionTag: encrypted.tag,
51
+ email: normalizedEmail,
52
+ nip05Username: username
53
+ });
54
+ await identity.initialize();
55
+ await identity.save();
56
+ return identity;
57
+ }
58
+ /**
59
+ * Link an existing Nostr identity (with encrypted keypair) to a profile
60
+ */
61
+ async linkToProfile(profile, nostrData) {
62
+ if (await this.findByPubkey(nostrData.pubkey)) throw new Error("Nostr identity with this public key already exists");
63
+ if (await this.findByEmail(nostrData.email)) throw new Error("Nostr identity with this email already exists");
64
+ const identity = new NostrIdentity({
65
+ ...this.options,
66
+ profileId: profile.id,
67
+ pubkey: nostrData.pubkey,
68
+ encryptedPrivkey: nostrData.encryptedPrivkey,
69
+ encryptionIv: nostrData.encryptionIv,
70
+ encryptionTag: nostrData.encryptionTag,
71
+ email: nostrData.email.toLowerCase(),
72
+ nip05Username: nostrData.nip05Username?.toLowerCase() || nostrData.email.toLowerCase().split("@")[0]
73
+ });
74
+ await identity.initialize();
75
+ await identity.save();
76
+ return identity;
77
+ }
78
+ /**
79
+ * Unlink a Nostr identity from a profile
80
+ */
81
+ async unlinkFromProfile(profileId, pubkey) {
82
+ const identity = await this.findOne({ where: {
83
+ profileId,
84
+ pubkey
85
+ } });
86
+ if (identity) {
87
+ await identity.delete();
88
+ return true;
89
+ }
90
+ return false;
91
+ }
92
+ /**
93
+ * Get NIP-05 response data for the /.well-known/nostr.json endpoint
94
+ * @param username - Optional username filter, returns all if not provided
95
+ */
96
+ async getNip05Response(username) {
97
+ const where = username ? { nip05Username: username.toLowerCase() } : {};
98
+ const identities = await this.list({ where });
99
+ const names = {};
100
+ for (const identity of identities) if (identity.nip05Username && identity.pubkey) names[identity.nip05Username] = identity.pubkey;
101
+ return { names };
102
+ }
103
+ /**
104
+ * Check if a NIP-05 username is available
105
+ */
106
+ async isNip05Available(username) {
107
+ return await this.findByNip05(username) === null;
108
+ }
109
+ /**
110
+ * Update NIP-05 username for an identity
111
+ */
112
+ async updateNip05Username(identity, newUsername) {
113
+ const normalizedUsername = newUsername.toLowerCase();
114
+ const existing = await this.findByNip05(normalizedUsername);
115
+ if (existing && existing.id !== identity.id) throw new Error(`NIP-05 username "${newUsername}" is already taken`);
116
+ identity.nip05Username = normalizedUsername;
117
+ await identity.save();
118
+ return identity;
119
+ }
120
+ };
121
+ //#endregion
122
+ export { NostrIdentityCollection_exports as n, NostrIdentityCollection as t };
123
+
124
+ //# sourceMappingURL=NostrIdentityCollection-BRYUgt47.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NostrIdentityCollection-BRYUgt47.js","names":[],"sources":["../../src/collections/NostrIdentityCollection.ts"],"sourcesContent":["/**\n * NostrIdentityCollection - Collection for managing Nostr identity records\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { encryptPrivkey, generateNostrKeypair } from '../auth/nostrCrypto';\nimport { NostrIdentity } from '../models/NostrIdentity';\nimport type { Profile } from '../models/Profile';\n\nexport interface Nip05Response {\n names: Record<string, string>;\n relays?: Record<string, string[]>;\n}\n\nexport class NostrIdentityCollection extends SmrtCollection<NostrIdentity> {\n static readonly _itemClass = NostrIdentity;\n\n /**\n * Find identities for a profile\n */\n async findByProfile(profileId: string): Promise<NostrIdentity[]> {\n return await this.list({\n where: { profileId },\n });\n }\n\n /**\n * Find identity by public key\n */\n async findByPubkey(pubkey: string): Promise<NostrIdentity | null> {\n return await this.findOne({\n where: { pubkey },\n });\n }\n\n /**\n * Find identity by email\n */\n async findByEmail(email: string): Promise<NostrIdentity | null> {\n return await this.findOne({\n where: { email: email.toLowerCase() },\n });\n }\n\n /**\n * Find identity by NIP-05 username\n */\n async findByNip05(username: string): Promise<NostrIdentity | null> {\n return await this.findOne({\n where: { nip05Username: username.toLowerCase() },\n });\n }\n\n /**\n * Link a new Nostr identity to a profile with a new keypair\n * @param profile - The profile to link to\n * @param email - Email address for this identity\n * @param masterSecret - Server master secret for encryption\n * @param nip05Username - Optional NIP-05 username (defaults to email local part)\n */\n async createForProfile(\n profile: Profile,\n email: string,\n masterSecret: string,\n nip05Username?: string,\n ): Promise<NostrIdentity> {\n const normalizedEmail = email.toLowerCase();\n\n // Check if already exists for this email\n const existing = await this.findByEmail(normalizedEmail);\n if (existing) {\n throw new Error(`Nostr identity already exists for email: ${email}`);\n }\n\n // Generate new keypair\n const keypair = generateNostrKeypair();\n\n // Encrypt private key\n const encrypted = encryptPrivkey(keypair.privkey, masterSecret);\n\n // Derive NIP-05 username from email if not provided\n const username =\n nip05Username?.toLowerCase() || normalizedEmail.split('@')[0];\n\n // Create identity\n const identity = new NostrIdentity({\n ...this.options,\n profileId: profile.id as string,\n pubkey: keypair.pubkey,\n encryptedPrivkey: encrypted.ciphertext,\n encryptionIv: encrypted.iv,\n encryptionTag: encrypted.tag,\n email: normalizedEmail,\n nip05Username: username,\n });\n\n await identity.initialize();\n await identity.save();\n\n return identity;\n }\n\n /**\n * Link an existing Nostr identity (with encrypted keypair) to a profile\n */\n async linkToProfile(\n profile: Profile,\n nostrData: {\n pubkey: string;\n encryptedPrivkey: string;\n encryptionIv: string;\n encryptionTag: string;\n email: string;\n nip05Username?: string;\n },\n ): Promise<NostrIdentity> {\n // Check if pubkey already exists\n const existingPubkey = await this.findByPubkey(nostrData.pubkey);\n if (existingPubkey) {\n throw new Error('Nostr identity with this public key already exists');\n }\n\n // Check if email already exists\n const existingEmail = await this.findByEmail(nostrData.email);\n if (existingEmail) {\n throw new Error('Nostr identity with this email already exists');\n }\n\n const identity = new NostrIdentity({\n ...this.options,\n profileId: profile.id as string,\n pubkey: nostrData.pubkey,\n encryptedPrivkey: nostrData.encryptedPrivkey,\n encryptionIv: nostrData.encryptionIv,\n encryptionTag: nostrData.encryptionTag,\n email: nostrData.email.toLowerCase(),\n nip05Username:\n nostrData.nip05Username?.toLowerCase() ||\n nostrData.email.toLowerCase().split('@')[0],\n });\n\n await identity.initialize();\n await identity.save();\n\n return identity;\n }\n\n /**\n * Unlink a Nostr identity from a profile\n */\n async unlinkFromProfile(profileId: string, pubkey: string): Promise<boolean> {\n const identity = await this.findOne({\n where: { profileId, pubkey },\n });\n\n if (identity) {\n await identity.delete();\n return true;\n }\n return false;\n }\n\n /**\n * Get NIP-05 response data for the /.well-known/nostr.json endpoint\n * @param username - Optional username filter, returns all if not provided\n */\n async getNip05Response(username?: string): Promise<Nip05Response> {\n const where = username ? { nip05Username: username.toLowerCase() } : {};\n\n const identities = await this.list({\n where,\n // Only return verified identities for NIP-05\n // Uncomment if you want to require verification:\n // where: { ...where, 'verifiedAt IS NOT': null },\n });\n\n const names: Record<string, string> = {};\n for (const identity of identities) {\n if (identity.nip05Username && identity.pubkey) {\n names[identity.nip05Username] = identity.pubkey;\n }\n }\n\n return { names };\n }\n\n /**\n * Check if a NIP-05 username is available\n */\n async isNip05Available(username: string): Promise<boolean> {\n const existing = await this.findByNip05(username);\n return existing === null;\n }\n\n /**\n * Update NIP-05 username for an identity\n */\n async updateNip05Username(\n identity: NostrIdentity,\n newUsername: string,\n ): Promise<NostrIdentity> {\n const normalizedUsername = newUsername.toLowerCase();\n\n // Check if username is available\n const existing = await this.findByNip05(normalizedUsername);\n if (existing && existing.id !== identity.id) {\n throw new Error(`NIP-05 username \"${newUsername}\" is already taken`);\n }\n\n identity.nip05Username = normalizedUsername;\n await identity.save();\n\n return identity;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ import { SmrtObject, field, foreignKey, smrt } from "@happyvertical/smrt-core";
2
+ //#region src/models/OidcIdentity.ts
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __decorateClass = (decorators, target, key, kind) => {
6
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
7
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+ var OidcIdentity = class extends SmrtObject {
12
+ profileId;
13
+ provider = "";
14
+ issuer = "";
15
+ subject = "";
16
+ identityKey = null;
17
+ email = "";
18
+ lastUsedAt = null;
19
+ constructor(options = {}) {
20
+ super(options);
21
+ if (options.profileId) this.profileId = options.profileId;
22
+ if (options.provider) this.provider = options.provider;
23
+ if (options.issuer) this.issuer = options.issuer;
24
+ if (options.subject) this.subject = options.subject;
25
+ if (options.email) this.email = options.email;
26
+ if (options.lastUsedAt !== void 0) this.lastUsedAt = options.lastUsedAt;
27
+ }
28
+ /**
29
+ * Get the linked Profile
30
+ */
31
+ async getProfile() {
32
+ return await this.getRelated("profileId");
33
+ }
34
+ /**
35
+ * Find identity by issuer and subject
36
+ */
37
+ static async findBySubject(issuer, subject, options = {}) {
38
+ const { OidcIdentityCollection } = await import("./OidcIdentityCollection-Ce4yv8p0.js").then((n) => n.r);
39
+ return await (await OidcIdentityCollection.create(options)).findBySubject(issuer, subject);
40
+ }
41
+ /** Build the collision-free natural key for one OIDC issuer subject. */
42
+ static buildIdentityKey(issuer, subject) {
43
+ return JSON.stringify([issuer, subject]);
44
+ }
45
+ /** Keep the durable key derived from its natural-key source fields. */
46
+ async save() {
47
+ this.identityKey = this.issuer.trim() && this.subject.trim() ? OidcIdentity.buildIdentityKey(this.issuer, this.subject) : null;
48
+ return super.save();
49
+ }
50
+ /**
51
+ * Reuse an existing exact identity for its unchanged Profile.
52
+ *
53
+ * @deprecated Authentication links must be created through transactional
54
+ * provisioning. This compatibility method only refreshes a unique mapping
55
+ * that already belongs to the supplied Profile, including legacy Profile
56
+ * types, and deliberately refuses to create or rebind authority.
57
+ */
58
+ static async findOrCreate(profile, oidcData, options = {}) {
59
+ const profileId = profile.id;
60
+ if (typeof profileId !== "string" || !profileId) throw new Error("OidcIdentity.findOrCreate() requires a saved Profile.");
61
+ const { reuseExistingOidcIdentityForProfile } = await import("./resolveIdentity-_h_2sGqJ.js").then((n) => n.i);
62
+ const profileOptions = profile.options ?? {};
63
+ return (await reuseExistingOidcIdentityForProfile(profileId, {
64
+ email: oidcData.email,
65
+ iss: oidcData.issuer,
66
+ sub: oidcData.subject
67
+ }, oidcData.provider, {
68
+ ...profileOptions,
69
+ ...options,
70
+ db: options.db ?? profileOptions.db
71
+ })).oidcIdentity;
72
+ }
73
+ /**
74
+ * Record usage of this identity
75
+ */
76
+ async recordUsage() {
77
+ this.lastUsedAt = /* @__PURE__ */ new Date();
78
+ await this.save();
79
+ }
80
+ };
81
+ __decorateClass([foreignKey("Profile", { required: true })], OidcIdentity.prototype, "profileId", 2);
82
+ __decorateClass([field({ type: "text" })], OidcIdentity.prototype, "provider", 2);
83
+ __decorateClass([field({
84
+ type: "text",
85
+ indexed: true
86
+ })], OidcIdentity.prototype, "issuer", 2);
87
+ __decorateClass([field({
88
+ type: "text",
89
+ indexed: true
90
+ })], OidcIdentity.prototype, "subject", 2);
91
+ __decorateClass([field({
92
+ type: "text",
93
+ nullable: true,
94
+ unique: true,
95
+ readonly: true
96
+ })], OidcIdentity.prototype, "identityKey", 2);
97
+ __decorateClass([field({ type: "text" })], OidcIdentity.prototype, "email", 2);
98
+ __decorateClass([field({
99
+ type: "datetime",
100
+ nullable: true
101
+ })], OidcIdentity.prototype, "lastUsedAt", 2);
102
+ OidcIdentity = __decorateClass([smrt({
103
+ tableName: "oidc_identities",
104
+ api: { include: ["list", "get"] },
105
+ mcp: { include: ["list", "get"] },
106
+ cli: { include: ["list", "get"] }
107
+ })], OidcIdentity);
108
+ //#endregion
109
+ export { OidcIdentity as t };
110
+
111
+ //# sourceMappingURL=OidcIdentity-ChkiT6hg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OidcIdentity-ChkiT6hg.js","names":[],"sources":["../../src/models/OidcIdentity.ts"],"sourcesContent":["/**\n * OidcIdentity - Links OIDC provider identities to Profile\n *\n * Stores the mapping between external OIDC providers (Keycloak, Google, GitHub)\n * and internal Profile records. Multiple identities can link to a single profile.\n */\n\nimport {\n field,\n foreignKey,\n SmrtObject,\n type SmrtObjectOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport type { Profile } from './Profile';\n\nexport interface OidcIdentityOptions extends SmrtObjectOptions {\n profileId?: string;\n provider?: string;\n issuer?: string;\n subject?: string;\n email?: string;\n lastUsedAt?: Date | null;\n}\n\n@smrt({\n tableName: 'oidc_identities',\n // Identity linking is an authentication authority change. Generated routes\n // authenticate callers but do not authorize Profile ownership, so mutations\n // must stay behind the trusted provisioning APIs below.\n api: { include: ['list', 'get'] },\n mcp: { include: ['list', 'get'] },\n cli: { include: ['list', 'get'] },\n})\nexport class OidcIdentity extends SmrtObject {\n /**\n * Link to the Profile (Person, Organization, Bot)\n */\n @foreignKey('Profile', { required: true })\n profileId?: string;\n\n /**\n * Provider name (e.g., 'keycloak', 'google', 'github')\n */\n @field({ type: 'text' })\n provider: string = '';\n\n /**\n * OIDC issuer URL (e.g., https://keycloak.example.com/realms/bmp)\n */\n @field({ type: 'text', indexed: true })\n issuer: string = '';\n\n /**\n * OIDC subject claim - unique identifier from the provider\n */\n @field({ type: 'text', indexed: true })\n subject: string = '';\n\n /**\n * Stable issuer+subject key used as the database race arbiter.\n *\n * Nullable for legacy rows; every newly linked or reused identity backfills\n * it. A separate unique constraint makes concurrent first login fail with a\n * retryable conflict instead of creating two identities.\n */\n @field({ type: 'text', nullable: true, unique: true, readonly: true })\n identityKey: string | null = null;\n\n /**\n * Cached email from the IdP (for display/lookup)\n */\n @field({ type: 'text' })\n email: string = '';\n\n /**\n * Last time this identity was used for authentication\n */\n @field({ type: 'datetime', nullable: true })\n lastUsedAt: Date | null = null;\n\n constructor(options: OidcIdentityOptions = {}) {\n super(options);\n if (options.profileId) this.profileId = options.profileId;\n if (options.provider) this.provider = options.provider;\n if (options.issuer) this.issuer = options.issuer;\n if (options.subject) this.subject = options.subject;\n if (options.email) this.email = options.email;\n if (options.lastUsedAt !== undefined) this.lastUsedAt = options.lastUsedAt;\n }\n\n /**\n * Get the linked Profile\n */\n async getProfile(): Promise<Profile | null> {\n return (await this.getRelated('profileId')) as Profile | null;\n }\n\n /**\n * Find identity by issuer and subject\n */\n static async findBySubject(\n issuer: string,\n subject: string,\n options: SmrtObjectOptions = {},\n ): Promise<OidcIdentity | null> {\n const { OidcIdentityCollection } = await import(\n '../collections/OidcIdentityCollection'\n );\n const collection = await OidcIdentityCollection.create(options);\n return await collection.findBySubject(issuer, subject);\n }\n\n /** Build the collision-free natural key for one OIDC issuer subject. */\n static buildIdentityKey(issuer: string, subject: string): string {\n return JSON.stringify([issuer, subject]);\n }\n\n /** Keep the durable key derived from its natural-key source fields. */\n override async save(): Promise<this> {\n this.identityKey =\n this.issuer.trim() && this.subject.trim()\n ? OidcIdentity.buildIdentityKey(this.issuer, this.subject)\n : null;\n return super.save();\n }\n\n /**\n * Reuse an existing exact identity for its unchanged Profile.\n *\n * @deprecated Authentication links must be created through transactional\n * provisioning. This compatibility method only refreshes a unique mapping\n * that already belongs to the supplied Profile, including legacy Profile\n * types, and deliberately refuses to create or rebind authority.\n */\n static async findOrCreate(\n profile: Profile,\n oidcData: {\n provider: string;\n issuer: string;\n subject: string;\n email?: string;\n },\n options: SmrtObjectOptions = {},\n ): Promise<OidcIdentity> {\n const profileId = profile.id;\n if (typeof profileId !== 'string' || !profileId) {\n throw new Error('OidcIdentity.findOrCreate() requires a saved Profile.');\n }\n const { reuseExistingOidcIdentityForProfile } = await import(\n '../auth/resolveIdentity'\n );\n const profileOptions = profile.options ?? {};\n const result = await reuseExistingOidcIdentityForProfile(\n profileId,\n {\n email: oidcData.email,\n iss: oidcData.issuer,\n sub: oidcData.subject,\n },\n oidcData.provider,\n {\n ...profileOptions,\n ...options,\n db: options.db ?? profileOptions.db,\n },\n );\n return result.oidcIdentity;\n }\n\n /**\n * Record usage of this identity\n */\n async recordUsage(): Promise<void> {\n this.lastUsedAt = new Date();\n await this.save();\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ import { y as __exportAll } from "./NostrIdentity-DF_a_WZk.js";
2
+ import { t as OidcIdentity } from "./OidcIdentity-ChkiT6hg.js";
3
+ import { SmrtCollection } from "@happyvertical/smrt-core";
4
+ //#region src/collections/OidcIdentityCollection.ts
5
+ var OidcIdentityCollection_exports = /* @__PURE__ */ __exportAll({
6
+ AmbiguousOidcIdentityError: () => AmbiguousOidcIdentityError,
7
+ OidcIdentityCollection: () => OidcIdentityCollection
8
+ });
9
+ var AmbiguousOidcIdentityError = class extends Error {
10
+ constructor(issuer, subject) {
11
+ super("Multiple OIDC identities match the same issuer and subject.");
12
+ this.issuer = issuer;
13
+ this.subject = subject;
14
+ this.name = "AmbiguousOidcIdentityError";
15
+ }
16
+ issuer;
17
+ subject;
18
+ };
19
+ var OidcIdentityCollection = class extends SmrtCollection {
20
+ static _itemClass = OidcIdentity;
21
+ /**
22
+ * Find identities for a profile
23
+ */
24
+ async findByProfile(profileId) {
25
+ return await this.list({ where: { profileId } });
26
+ }
27
+ /**
28
+ * Find identity by issuer and subject
29
+ */
30
+ async findBySubject(issuer, subject) {
31
+ const matches = await this.list({
32
+ where: {
33
+ issuer,
34
+ subject
35
+ },
36
+ limit: 2
37
+ });
38
+ if (matches.length > 1) throw new AmbiguousOidcIdentityError(issuer, subject);
39
+ return matches[0] ?? null;
40
+ }
41
+ /**
42
+ * Find identities by provider
43
+ */
44
+ async findByProvider(provider) {
45
+ return await this.list({ where: { provider } });
46
+ }
47
+ /**
48
+ * Reuse an existing exact OIDC identity for its unchanged Profile.
49
+ *
50
+ * @deprecated New authentication links require the owner-aware,
51
+ * transactional provisioning APIs. This compatibility helper may refresh a
52
+ * legacy Profile type, but refuses to create or rebind authority.
53
+ */
54
+ async linkToProfile(profile, oidcData) {
55
+ return OidcIdentity.findOrCreate(profile, oidcData, this.options);
56
+ }
57
+ /**
58
+ * Unlink an OIDC identity from a profile
59
+ */
60
+ async unlinkFromProfile(profileId, issuer, subject) {
61
+ const identity = await this.findOne({ where: {
62
+ profileId,
63
+ issuer,
64
+ subject
65
+ } });
66
+ if (identity) {
67
+ await identity.delete();
68
+ return true;
69
+ }
70
+ return false;
71
+ }
72
+ };
73
+ //#endregion
74
+ export { OidcIdentityCollection as n, OidcIdentityCollection_exports as r, AmbiguousOidcIdentityError as t };
75
+
76
+ //# sourceMappingURL=OidcIdentityCollection-Ce4yv8p0.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OidcIdentityCollection-Ce4yv8p0.js","names":[],"sources":["../../src/collections/OidcIdentityCollection.ts"],"sourcesContent":["/**\n * OidcIdentityCollection - Collection for managing OIDC identity records\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { OidcIdentity } from '../models/OidcIdentity';\nimport type { Profile } from '../models/Profile';\n\n/** More than one legacy row maps the same opaque OIDC issuer and subject. */\nexport class AmbiguousOidcIdentityError extends Error {\n constructor(\n readonly issuer: string,\n readonly subject: string,\n ) {\n super('Multiple OIDC identities match the same issuer and subject.');\n this.name = 'AmbiguousOidcIdentityError';\n }\n}\n\nexport class OidcIdentityCollection extends SmrtCollection<OidcIdentity> {\n static readonly _itemClass = OidcIdentity;\n\n /**\n * Find identities for a profile\n */\n async findByProfile(profileId: string): Promise<OidcIdentity[]> {\n return await this.list({\n where: { profileId },\n });\n }\n\n /**\n * Find identity by issuer and subject\n */\n async findBySubject(\n issuer: string,\n subject: string,\n ): Promise<OidcIdentity | null> {\n const matches = await this.list({\n where: { issuer, subject },\n limit: 2,\n });\n if (matches.length > 1) {\n throw new AmbiguousOidcIdentityError(issuer, subject);\n }\n return matches[0] ?? null;\n }\n\n /**\n * Find identities by provider\n */\n async findByProvider(provider: string): Promise<OidcIdentity[]> {\n return await this.list({\n where: { provider },\n });\n }\n\n /**\n * Reuse an existing exact OIDC identity for its unchanged Profile.\n *\n * @deprecated New authentication links require the owner-aware,\n * transactional provisioning APIs. This compatibility helper may refresh a\n * legacy Profile type, but refuses to create or rebind authority.\n */\n async linkToProfile(\n profile: Profile,\n oidcData: {\n provider: string;\n issuer: string;\n subject: string;\n email?: string;\n },\n ): Promise<OidcIdentity> {\n return OidcIdentity.findOrCreate(profile, oidcData, this.options);\n }\n\n /**\n * Unlink an OIDC identity from a profile\n */\n async unlinkFromProfile(\n profileId: string,\n issuer: string,\n subject: string,\n ): Promise<boolean> {\n const identity = await this.findOne({\n where: { profileId, issuer, subject },\n });\n\n if (identity) {\n await identity.delete();\n return true;\n }\n return false;\n }\n}\n"],"mappings":";;;;;;;;AASO,IAAM,6BAAN,cAAyC,MAAM;CACpD,YACW,QACA,SACT;EACA,MAAM,6DAA6D;EAH1D,KAAA,SAAA;EACA,KAAA,UAAA;EAGT,KAAK,OAAO;CACd;CALW;CACA;AAKb;AAEO,IAAM,yBAAN,cAAqC,eAA6B;CACvE,OAAgB,aAAa;;;;CAK7B,MAAM,cAAc,WAA4C;EAC9D,OAAO,MAAM,KAAK,KAAK,EACrB,OAAO,EAAE,UAAU,EACrB,CAAC;CACH;;;;CAKA,MAAM,cACJ,QACA,SAC8B;EAC9B,MAAM,UAAU,MAAM,KAAK,KAAK;GAC9B,OAAO;IAAE;IAAQ;GAAQ;GACzB,OAAO;EACT,CAAC;EACD,IAAI,QAAQ,SAAS,GACnB,MAAM,IAAI,2BAA2B,QAAQ,OAAO;EAEtD,OAAO,QAAQ,MAAM;CACvB;;;;CAKA,MAAM,eAAe,UAA2C;EAC9D,OAAO,MAAM,KAAK,KAAK,EACrB,OAAO,EAAE,SAAS,EACpB,CAAC;CACH;;;;;;;;CASA,MAAM,cACJ,SACA,UAMuB;EACvB,OAAO,aAAa,aAAa,SAAS,UAAU,KAAK,OAAO;CAClE;;;;CAKA,MAAM,kBACJ,WACA,QACA,SACkB;EAClB,MAAM,WAAW,MAAM,KAAK,QAAQ,EAClC,OAAO;GAAE;GAAW;GAAQ;EAAQ,EACtC,CAAC;EAED,IAAI,UAAU;GACZ,MAAM,SAAS,OAAO;GACtB,OAAO;EACT;EACA,OAAO;CACT;AACF"}
@@ -1,4 +1,4 @@
1
- import { x as __exportAll } from "./NostrIdentityCollection-CG6HGf6c.js";
1
+ import { y as __exportAll } from "./NostrIdentity-DF_a_WZk.js";
2
2
  import { SmrtJunction, SmrtObject, crossPackageRef, field, foreignKey, smrt } from "@happyvertical/smrt-core";
3
3
  import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
4
4
  import { addOwnedAssetFromCollection, getOwnedAssetsFromCollection, removeOwnedAssetFromCollection } from "@happyvertical/smrt-assets";
@@ -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-BOMOloTL.js").then((n) => n.n);
69
+ const { ProfileCollection } = await import("./ProfileCollection-Bix9yAiy.js").then((n) => n.r);
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-DzUwbtTq.js.map
93
+ //# sourceMappingURL=ProfileAssetCollection-CtlvXbGy.js.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"ProfileAssetCollection-CtlvXbGy.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"}