@happyvertical/smrt-cli 0.51.15 → 0.51.16

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 (27) hide show
  1. package/AGENTS.md +1 -0
  2. package/dist/ApiKey-BZmbOTnL-Dz-PO2sa.js +157 -0
  3. package/dist/ApiKeyCollection-Bfb_nw81-Dks_0546.js +2 -0
  4. package/dist/AuditLogCollection-B1YonCAP-Db9tLDtf.js +2 -0
  5. package/dist/NostrIdentity-n70E0vck-CZwoR7Rs.js +4189 -0
  6. package/dist/NostrIdentityCollection-Ca74dkaL-DESXyrjY.js +2 -0
  7. package/dist/OidcIdentity-CChQnYNH-DV9jiJ5r.js +117 -0
  8. package/dist/OidcIdentityCollection-D3L0WYbM-C85jz9CB.js +2 -0
  9. package/dist/ProfileAssetCollection-V8u4wqSL-BKz6v8py.js +2 -0
  10. package/dist/ProfileCollection-BvhD2cfL-CT6ctQ3l.js +1753 -0
  11. package/dist/ProfileMetadataCollection-DxX_BJKV-StatXt-E.js +2 -0
  12. package/dist/ProfileMetafieldCollection-CMKhVi2I--ba-T4qA.js +2 -0
  13. package/dist/ProfileRelationshipCollection-DUVgaIqf-B4TDc4TQ.js +2 -0
  14. package/dist/ProfileRelationshipTermCollection-DtA9OM23-1ZkGUm00.js +2 -0
  15. package/dist/ProfileRelationshipType-BRiRka6t-Dw1efEX5.js +101 -0
  16. package/dist/ProfileRelationshipTypeCollection-BdXwd7Kf-GEu0alx6.js +2 -0
  17. package/dist/{commands-BhDS-TVS.js → commands-CL85adn5.js} +95 -21
  18. package/dist/{config-DfBo4hHU.js → config-hqNo4lzH.js} +1 -13
  19. package/dist/dist-9EA_5mOX.js +142 -0
  20. package/dist/dist-BIA7A6wF.js +3712 -0
  21. package/dist/dist-mLcl4Ttr.js +1487 -0
  22. package/dist/index.js +14 -14
  23. package/dist/{json-validator-CjX8fHx9.js → json-validator-oEN7eUI3.js} +1 -1
  24. package/dist/oidcProvisioningPrimitives-CjOkg3Zd-DhC5lyKE.js +161 -0
  25. package/dist/resolveIdentity-73HQnTlT-DQB4z5h5.js +2 -0
  26. package/dist/rolldown-runtime-8H4AJuhK.js +14 -0
  27. package/package.json +8 -7
@@ -0,0 +1,1753 @@
1
+ import { t as __exportAll$1 } from "./rolldown-runtime-8H4AJuhK.js";
2
+ import { n as __exportAll$2 } from "./NostrIdentity-n70E0vck-CZwoR7Rs.js";
3
+ import { a as getTenantId, i as queryWithGlobals, n as tenantId, r as queryGlobal, s as withSystemContext, t as TenantScoped } from "./dist-9EA_5mOX.js";
4
+ import { a as removeOwnedAssetFromCollection, i as getOwnedAssetsFromCollection, n as assertValidOwnedAssetRelationship, o as resolveOwnedAssetsById, r as assertValidOwnedAssetSortOrder, t as addOwnedAssetFromCollection } from "./dist-mLcl4Ttr.js";
5
+ import { ObjectRegistry, SmrtCollection, SmrtObject, ValidationError, field, foreignKey, isAbortedTransactionError, oneToMany, smrt } from "@happyvertical/smrt-core";
6
+ import { getPackageConfig } from "@happyvertical/smrt-config";
7
+ import { BackfillTableUnavailableError, BackfillTracker } from "@happyvertical/smrt-core/migrations";
8
+ //#region ../prompts/dist/index.js
9
+ var __defProp$1$1 = Object.defineProperty;
10
+ var __exportAll = (all, no_symbols) => {
11
+ let target = {};
12
+ for (var name in all) __defProp$1$1(target, name, {
13
+ get: all[name],
14
+ enumerable: true
15
+ });
16
+ if (!no_symbols) __defProp$1$1(target, Symbol.toStringTag, { value: "Module" });
17
+ return target;
18
+ };
19
+ ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-prompts\",\"packageVersion\":\"0.51.16\",\"objects\":{\"@happyvertical/smrt-prompts:PromptOverrideCollection\":{\"name\":\"promptoverridecollection\",\"className\":\"PromptOverrideCollection\",\"qualifiedName\":\"@happyvertical/smrt-prompts:PromptOverrideCollection\",\"collection\":\"promptoverrides\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/prompts/src/collections/PromptOverrideCollection.ts\",\"packageName\":\"@happyvertical/smrt-prompts\",\"fields\":{},\"methods\":{\"getAppOverride\":{\"name\":\"getAppOverride\",\"async\":true,\"parameters\":[{\"name\":\"key\",\"type\":\"string\",\"optional\":false},{\"name\":\"options\",\"type\":\"object\",\"optional\":true,\"memberTypes\":[\"string\"]}],\"returnType\":\"Promise<PromptOverride | null>\",\"isStatic\":false,\"isPublic\":true},\"getTenantOverride\":{\"name\":\"getTenantOverride\",\"async\":true,\"parameters\":[{\"name\":\"key\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false},{\"name\":\"options\",\"type\":\"object\",\"optional\":true,\"memberTypes\":[\"string\"]}],\"returnType\":\"Promise<PromptOverride | null>\",\"isStatic\":false,\"isPublic\":true},\"getResolutionLayers\":{\"name\":\"getResolutionLayers\",\"async\":true,\"parameters\":[{\"name\":\"key\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":true,\"unionBranches\":[{\"type\":\"string\"},{\"type\":\"null\"}]},{\"name\":\"options\",\"type\":\"object\",\"optional\":true,\"memberTypes\":[\"string\"]}],\"returnType\":\"Promise<object>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"_smrt_prompt_overrides\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"PromptOverride\",\"exportName\":\"PromptOverrideCollection\",\"collectionExportName\":\"PromptOverrideCollectionCollection\",\"schema\":{\"tableName\":\"_smrt_prompt_overrides\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_prompt_overrides\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"key\\\" TEXT NOT NULL DEFAULT '',\\n \\\"tenant_id\\\" TEXT,\\n \\\"template\\\" TEXT,\\n \\\"profile\\\" TEXT,\\n \\\"model\\\" TEXT,\\n \\\"params\\\" TEXT\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"key\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"tenant_id\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"template\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"profile\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"model\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"params\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"_smrt_prompt_overrides_key_context_idx\",\"columns\":[\"key\",\"context\"],\"unique\":true},{\"name\":\"_smrt_prompt_overrides_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"_smrt_prompt_overrides_created_at_idx\",\"columns\":[\"created_at\"]}],\"version\":\"6f475427\"},\"importPath\":\"@happyvertical/smrt-prompts\"},\"@happyvertical/smrt-prompts:PromptOverride\":{\"name\":\"promptoverride\",\"className\":\"PromptOverride\",\"qualifiedName\":\"@happyvertical/smrt-prompts:PromptOverride\",\"collection\":\"promptoverrides\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/prompts/src/models/PromptOverride.ts\",\"packageName\":\"@happyvertical/smrt-prompts\",\"fields\":{\"key\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"template\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"profile\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"model\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"params\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}}},\"methods\":{\"getParams\":{\"name\":\"getParams\",\"async\":false,\"parameters\":[],\"returnType\":\"PromptParams\",\"isStatic\":false,\"isPublic\":true},\"setParams\":{\"name\":\"setParams\",\"async\":false,\"parameters\":[{\"name\":\"params\",\"type\":\"PromptParams | null\",\"optional\":false,\"unionBranches\":[{\"type\":\"PromptParams\"},{\"type\":\"null\"}]}],\"returnType\":\"void\",\"isStatic\":false,\"isPublic\":true},\"toPromptLayer\":{\"name\":\"toPromptLayer\",\"async\":false,\"parameters\":[],\"returnType\":\"PromptLayer\",\"isStatic\":false,\"isPublic\":true},\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<this>\",\"isStatic\":false,\"isPublic\":true},\"delete\":{\"name\":\"delete\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"_smrt_prompt_overrides\",\"conflictColumns\":[\"key\",\"context\"],\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\"]},\"cli\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\"],\"exclude\":[\"getParams\",\"setParams\",\"toPromptLayer\"]},\"mcp\":{\"include\":[]}},\"extends\":\"SmrtObject\",\"exportName\":\"PromptOverride\",\"collectionExportName\":\"PromptOverrideCollection\",\"validationRules\":[{\"field\":\"key\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"_smrt_prompt_overrides\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_prompt_overrides\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"key\\\" TEXT NOT NULL DEFAULT '',\\n \\\"tenant_id\\\" TEXT,\\n \\\"template\\\" TEXT,\\n \\\"profile\\\" TEXT,\\n \\\"model\\\" TEXT,\\n \\\"params\\\" TEXT\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"key\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"tenant_id\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"template\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"profile\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"model\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"params\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"_smrt_prompt_overrides_key_context_idx\",\"columns\":[\"key\",\"context\"],\"unique\":true},{\"name\":\"_smrt_prompt_overrides_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"_smrt_prompt_overrides_created_at_idx\",\"columns\":[\"created_at\"]}],\"version\":\"6f475427\"},\"importPath\":\"@happyvertical/smrt-prompts\"}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-core\",\"@happyvertical/smrt-tenancy\"]}"));
20
+ var PROMPT_CACHE_TTL_MS = 3e4;
21
+ var promptCache = /* @__PURE__ */ new Map();
22
+ var cacheGenerations = /* @__PURE__ */ new Map();
23
+ var nextGeneration = 1;
24
+ var clearedThrough = 0;
25
+ var dbInstanceIds = /* @__PURE__ */ new WeakMap();
26
+ var nextDbId = 1;
27
+ function getDbNamespace(db) {
28
+ if (!db) return "no-db";
29
+ if (typeof db === "string") return `db:${db}`;
30
+ if (typeof db === "object") {
31
+ const dbObject = db;
32
+ if (typeof dbObject.query === "function") {
33
+ if (!dbInstanceIds.has(dbObject)) dbInstanceIds.set(dbObject, `db-instance:${nextDbId++}`);
34
+ const namespace = dbInstanceIds.get(dbObject);
35
+ if (namespace) return namespace;
36
+ return "db-instance:unknown";
37
+ }
38
+ try {
39
+ return `db-config:${JSON.stringify(dbObject)}`;
40
+ } catch {
41
+ return "db-config:opaque";
42
+ }
43
+ }
44
+ return "db:unknown";
45
+ }
46
+ function buildCacheKey(key, tenantId, db) {
47
+ return `${getDbNamespace(db)}::${key}::${tenantId ?? "app"}`;
48
+ }
49
+ function buildGenerationKey(key, db) {
50
+ return `${getDbNamespace(db)}::${key}`;
51
+ }
52
+ function bumpGeneration(key, db) {
53
+ cacheGenerations.set(buildGenerationKey(key, db), nextGeneration++);
54
+ }
55
+ function getCachedPromptBase(key, tenantId, db) {
56
+ const cacheKey = buildCacheKey(key, tenantId, db);
57
+ const cached = promptCache.get(cacheKey);
58
+ if (!cached) return null;
59
+ if (cached.expiresAt <= Date.now()) {
60
+ promptCache.delete(cacheKey);
61
+ return null;
62
+ }
63
+ return cached.value;
64
+ }
65
+ function getPromptCacheGeneration(key, db) {
66
+ const generation = cacheGenerations.get(buildGenerationKey(key, db)) ?? 0;
67
+ return generation > clearedThrough ? generation : clearedThrough;
68
+ }
69
+ function setCachedPromptBase(key, tenantId, db, value, loadedAtGeneration) {
70
+ if (getPromptCacheGeneration(key, db) !== loadedAtGeneration) return;
71
+ promptCache.set(buildCacheKey(key, tenantId, db), {
72
+ expiresAt: Date.now() + PROMPT_CACHE_TTL_MS,
73
+ value
74
+ });
75
+ }
76
+ function invalidatePromptCache(key, tenantId, db) {
77
+ const dbNamespace = getDbNamespace(db);
78
+ bumpGeneration(key, db);
79
+ if (tenantId !== null && tenantId !== void 0) {
80
+ promptCache.delete(buildCacheKey(key, tenantId, db));
81
+ return;
82
+ }
83
+ const keyPrefix = `${dbNamespace}::${key}::`;
84
+ for (const cacheKey of promptCache.keys()) if (cacheKey.startsWith(keyPrefix)) promptCache.delete(cacheKey);
85
+ }
86
+ var DEFAULT_EDITABLE = {
87
+ template: false,
88
+ profile: false,
89
+ model: false,
90
+ params: false
91
+ };
92
+ var TOP_LEVEL_RESERVED_KEYS = /* @__PURE__ */ new Set([
93
+ "key",
94
+ "editable",
95
+ "template",
96
+ "ai",
97
+ "profile",
98
+ "provider",
99
+ "model",
100
+ "params",
101
+ "temperature",
102
+ "maxTokens"
103
+ ]);
104
+ var AI_RESERVED_KEYS = /* @__PURE__ */ new Set([
105
+ "profile",
106
+ "provider",
107
+ "model",
108
+ "params",
109
+ "temperature",
110
+ "maxTokens"
111
+ ]);
112
+ var AI_ROUTING_PARAM_KEYS = /* @__PURE__ */ new Set([
113
+ "profile",
114
+ "provider",
115
+ "type",
116
+ "model",
117
+ "defaultModel"
118
+ ]);
119
+ function isPlainObject(value) {
120
+ return !!value && typeof value === "object" && !Array.isArray(value);
121
+ }
122
+ function normalizeEditableConfig(editable) {
123
+ return {
124
+ template: editable?.template ?? DEFAULT_EDITABLE.template,
125
+ profile: editable?.profile ?? DEFAULT_EDITABLE.profile,
126
+ model: editable?.model ?? DEFAULT_EDITABLE.model,
127
+ params: editable?.params ?? DEFAULT_EDITABLE.params
128
+ };
129
+ }
130
+ function extractPromptParams(source, reservedKeys) {
131
+ const params = {};
132
+ if (isPlainObject(source.params)) Object.assign(params, sanitizePromptParams(source.params));
133
+ if ("temperature" in source && source.temperature !== void 0) params.temperature = source.temperature;
134
+ if ("maxTokens" in source && source.maxTokens !== void 0) params.maxTokens = source.maxTokens;
135
+ for (const [key, value] of Object.entries(source)) {
136
+ if (reservedKeys.has(key) || value === void 0) continue;
137
+ assignPromptParam(params, key, value);
138
+ }
139
+ return params;
140
+ }
141
+ function assignPromptParam(params, key, value) {
142
+ if (AI_ROUTING_PARAM_KEYS.has(key) || value === void 0) return;
143
+ params[key] = value;
144
+ }
145
+ function sanitizePromptParams(params) {
146
+ const sanitized = {};
147
+ if (!params) return sanitized;
148
+ for (const [key, value] of Object.entries(params)) assignPromptParam(sanitized, key, value);
149
+ return sanitized;
150
+ }
151
+ function mergePromptParams(...paramLayers) {
152
+ const merged = {};
153
+ for (const layer of paramLayers) {
154
+ if (!layer) continue;
155
+ for (const [key, value] of Object.entries(layer)) assignPromptParam(merged, key, value);
156
+ }
157
+ return merged;
158
+ }
159
+ function normalizeAIInput(ai) {
160
+ if (!isPlainObject(ai)) return { params: {} };
161
+ const profile = typeof ai.profile === "string" || ai.profile === null ? ai.profile : void 0;
162
+ const model = typeof ai.model === "string" || ai.model === null ? ai.model : void 0;
163
+ return {
164
+ profile: profile ?? void 0,
165
+ model: model ?? void 0,
166
+ params: extractPromptParams(ai, AI_RESERVED_KEYS)
167
+ };
168
+ }
169
+ function normalizePromptDefinitionInput(input) {
170
+ return {
171
+ key: input.key,
172
+ template: input.template,
173
+ ai: normalizeAIInput(input.ai),
174
+ editable: normalizeEditableConfig(input.editable)
175
+ };
176
+ }
177
+ function normalizePromptLayer(input) {
178
+ if (!isPlainObject(input)) return {};
179
+ const nestedAi = isPlainObject(input.ai) ? normalizeAIInput(input.ai) : { params: {} };
180
+ const topLevelParams = extractPromptParams(input, TOP_LEVEL_RESERVED_KEYS);
181
+ return {
182
+ template: input.template === void 0 ? void 0 : input.template === null ? null : String(input.template),
183
+ profile: input.profile === void 0 ? nestedAi.profile : input.profile === null ? null : String(input.profile),
184
+ model: input.model === void 0 ? nestedAi.model : input.model === null ? null : String(input.model),
185
+ params: mergePromptParams(topLevelParams, nestedAi.params)
186
+ };
187
+ }
188
+ function mergePromptLayers(...layers) {
189
+ let template = "";
190
+ let profile;
191
+ let model;
192
+ let params = {};
193
+ for (const layer of layers) {
194
+ if (!layer) continue;
195
+ if (layer.template !== void 0 && layer.template !== null) template = layer.template;
196
+ if (layer.profile !== void 0 && layer.profile !== null) profile = layer.profile;
197
+ if (layer.model !== void 0 && layer.model !== null) model = layer.model;
198
+ if (layer.params !== void 0 && layer.params !== null) params = mergePromptParams(params, layer.params);
199
+ }
200
+ return {
201
+ template,
202
+ ai: {
203
+ profile,
204
+ model,
205
+ params
206
+ }
207
+ };
208
+ }
209
+ function normalizeProfileConfig(profile) {
210
+ const params = extractPromptParams(isPlainObject(profile) ? profile : {}, /* @__PURE__ */ new Set(["provider", "model"]));
211
+ return {
212
+ provider: String(profile.provider),
213
+ model: String(profile.model),
214
+ params
215
+ };
216
+ }
217
+ function validateProfileName(profileName, config) {
218
+ if (profileName.trim() === "") throw new Error("Prompt profile names cannot be empty");
219
+ const allowedProfileNames = config.allowedProfileNames;
220
+ if (Array.isArray(allowedProfileNames) && !allowedProfileNames.includes(profileName)) throw new Error(`Prompt profile "${profileName}" is not in the allowed profile list`);
221
+ if (!config.profiles?.[profileName]) throw new Error(`Prompt profile "${profileName}" is not configured`);
222
+ }
223
+ function validateModelName(modelName, config) {
224
+ if (modelName.trim() === "") throw new Error("Prompt model overrides cannot be empty");
225
+ const allowedModels = config.allowedModels;
226
+ if (Array.isArray(allowedModels) && !allowedModels.includes(modelName)) throw new Error(`Prompt model "${modelName}" is not in the allowed model list`);
227
+ }
228
+ function buildResolvedAI(ai, config) {
229
+ let provider;
230
+ let model = ai.model;
231
+ let params = ai.params;
232
+ if (ai.profile) {
233
+ validateProfileName(ai.profile, config);
234
+ const configuredProfile = config.profiles?.[ai.profile];
235
+ if (!configuredProfile) throw new Error(`Prompt profile "${ai.profile}" is not configured`);
236
+ const profileConfig = normalizeProfileConfig(configuredProfile);
237
+ provider = profileConfig.provider;
238
+ model = model ?? profileConfig.model;
239
+ params = mergePromptParams(profileConfig.params, params);
240
+ }
241
+ if (ai.model) validateModelName(ai.model, config);
242
+ if (ai.model && !ai.profile) throw new Error("Direct prompt model overrides require an effective prompt profile");
243
+ if (model && !ai.model && config.allowedModels) validateModelName(model, config);
244
+ const resolved = {
245
+ ...params,
246
+ profile: ai.profile,
247
+ provider,
248
+ model,
249
+ ...config.allowedModels ? { allowedModels: [...config.allowedModels] } : {},
250
+ params
251
+ };
252
+ if (typeof params.temperature === "number") resolved.temperature = params.temperature;
253
+ if (typeof params.maxTokens === "number") resolved.maxTokens = params.maxTokens;
254
+ return resolved;
255
+ }
256
+ function renderPromptTemplate(template, variables) {
257
+ if (!variables || Object.keys(variables).length === 0) return template;
258
+ return template.replace(/\{([^{}]+)\}/g, (_match, rawKey) => {
259
+ const value = variables[rawKey.trim()];
260
+ if (value === void 0 || value === null) return "";
261
+ if (value instanceof Date) return value.toISOString();
262
+ if (Array.isArray(value) || isPlainObject(value)) try {
263
+ return JSON.stringify(value);
264
+ } catch {
265
+ return "";
266
+ }
267
+ return String(value);
268
+ });
269
+ }
270
+ function parsePromptParams(raw) {
271
+ if (!raw) return {};
272
+ try {
273
+ const parsed = JSON.parse(raw);
274
+ return isPlainObject(parsed) ? sanitizePromptParams(parsed) : {};
275
+ } catch {
276
+ return {};
277
+ }
278
+ }
279
+ function serializePromptParams(params) {
280
+ if (params === null || params === void 0) return null;
281
+ return JSON.stringify(sanitizePromptParams(params));
282
+ }
283
+ function getRegistry() {
284
+ if (!globalThis.__smrtPromptRegistry) globalThis.__smrtPromptRegistry = /* @__PURE__ */ new Map();
285
+ return globalThis.__smrtPromptRegistry;
286
+ }
287
+ function stableStringify(value) {
288
+ if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
289
+ if (value && typeof value === "object") return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(",")}}`;
290
+ return JSON.stringify(value);
291
+ }
292
+ var PromptRegistry = {
293
+ register(input) {
294
+ if (!input.key || input.key.trim() === "") throw new Error("Prompt definitions require a non-empty key");
295
+ const definition = normalizePromptDefinitionInput(input);
296
+ const registry = getRegistry();
297
+ const existing = registry.get(definition.key);
298
+ if (existing) {
299
+ if (stableStringify(existing) !== stableStringify(definition)) throw new Error(`Prompt "${definition.key}" is already registered with a different definition`);
300
+ return existing;
301
+ }
302
+ registry.set(definition.key, definition);
303
+ return definition;
304
+ },
305
+ get(key) {
306
+ return getRegistry().get(key);
307
+ },
308
+ has(key) {
309
+ return getRegistry().has(key);
310
+ },
311
+ getAll() {
312
+ return Array.from(getRegistry().values());
313
+ },
314
+ clear() {
315
+ getRegistry().clear();
316
+ }
317
+ };
318
+ function definePrompt(input) {
319
+ return PromptRegistry.register(input);
320
+ }
321
+ var __defProp$3 = Object.defineProperty;
322
+ var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
323
+ var __decorateClass$3 = (decorators, target, key, kind) => {
324
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
325
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
326
+ if (kind && result) __defProp$3(target, key, result);
327
+ return result;
328
+ };
329
+ function getPromptConfig$1() {
330
+ return getPackageConfig("prompts", {
331
+ profiles: {},
332
+ prompts: {}
333
+ });
334
+ }
335
+ var PromptOverride = class extends SmrtObject {
336
+ key = "";
337
+ tenantId = null;
338
+ template = null;
339
+ profile = null;
340
+ model = null;
341
+ params = null;
342
+ constructor(options = {}) {
343
+ super(options);
344
+ if (options.key !== void 0) this.key = options.key;
345
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
346
+ if (options.template !== void 0) this.template = options.template;
347
+ if (options.profile !== void 0) this.profile = options.profile;
348
+ if (options.model !== void 0) this.model = options.model;
349
+ if (options.params !== void 0) if (typeof options.params === "string" || options.params === null) this.params = options.params;
350
+ else this.params = serializePromptParams(options.params);
351
+ }
352
+ getParams() {
353
+ return parsePromptParams(this.params);
354
+ }
355
+ setParams(params) {
356
+ this.params = serializePromptParams(params);
357
+ }
358
+ toPromptLayer() {
359
+ return {
360
+ template: this.template,
361
+ profile: this.profile,
362
+ model: this.model,
363
+ params: this.params === null ? null : this.getParams()
364
+ };
365
+ }
366
+ async save() {
367
+ const previousIdentity = await this.getPersistedIdentity();
368
+ this.normalizeParamsForPersistence();
369
+ await this.validatePromptOverride();
370
+ this.context = this.tenantId ?? "__app__";
371
+ const identityChanged = previousIdentity && (previousIdentity.key !== this.key || previousIdentity.tenantId !== this.tenantId);
372
+ const result = identityChanged && previousIdentity ? await this.saveAfterIdentityChange() : await super.save();
373
+ if (identityChanged && previousIdentity) invalidatePromptCache(previousIdentity.key, previousIdentity.tenantId, this.db);
374
+ invalidatePromptCache(this.key, this.tenantId, this.db);
375
+ return result;
376
+ }
377
+ async saveAfterIdentityChange() {
378
+ if (typeof this.db.beginTransaction === "function") return this.saveAfterIdentityChangeInTransaction();
379
+ return this.saveAfterIdentityChangeWithDeferredDelete();
380
+ }
381
+ async saveAfterIdentityChangeInTransaction() {
382
+ const originalDb = this._db;
383
+ const originalOptionsDb = this.options.db;
384
+ const tx = await this.db.beginTransaction?.();
385
+ if (!tx) return this.saveAfterIdentityChangeWithDeferredDelete();
386
+ try {
387
+ this._db = tx;
388
+ this.options.db = tx;
389
+ await super.delete();
390
+ const result = await super.save();
391
+ await tx.commit();
392
+ return result;
393
+ } catch (error) {
394
+ try {
395
+ await tx.rollback();
396
+ } catch {}
397
+ throw error;
398
+ } finally {
399
+ this._db = originalDb;
400
+ this.options.db = originalOptionsDb;
401
+ }
402
+ }
403
+ async saveAfterIdentityChangeWithDeferredDelete() {
404
+ const previousId = this.id;
405
+ if (!previousId) return super.save();
406
+ const replacementId = crypto.randomUUID();
407
+ let replacementSaved = false;
408
+ this.id = replacementId;
409
+ try {
410
+ const result = await super.save();
411
+ replacementSaved = true;
412
+ await this.db.delete(this.tableName, { id: previousId });
413
+ return result;
414
+ } catch (error) {
415
+ if (replacementSaved) try {
416
+ await this.db.delete(this.tableName, { id: replacementId });
417
+ } catch {}
418
+ this.id = previousId;
419
+ throw error;
420
+ }
421
+ }
422
+ async delete() {
423
+ const key = this.key;
424
+ const tenantId = this.tenantId;
425
+ await super.delete();
426
+ invalidatePromptCache(key, tenantId, this.db);
427
+ }
428
+ async validatePromptOverride() {
429
+ if (!this.key || this.key.trim() === "") throw new Error("PromptOverride.key is required");
430
+ const definition = PromptRegistry.get(this.key);
431
+ if (!definition) throw new Error(`Unknown prompt key "${this.key}"`);
432
+ const config = getPromptConfig$1();
433
+ const editable = definition.editable;
434
+ if (this.template !== null && !editable.template) throw new Error(`Prompt "${this.key}" does not allow template overrides`);
435
+ if (this.profile !== null && !editable.profile) throw new Error(`Prompt "${this.key}" does not allow profile overrides`);
436
+ if (this.model !== null && !editable.model) throw new Error(`Prompt "${this.key}" does not allow model overrides`);
437
+ if (this.params !== null && !editable.params) throw new Error(`Prompt "${this.key}" does not allow params overrides`);
438
+ if (this.params !== null) try {
439
+ const parsed = JSON.parse(this.params);
440
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Prompt params must be a JSON object");
441
+ } catch (error) {
442
+ throw new Error(`Prompt "${this.key}" has invalid params JSON: ${error instanceof Error ? error.message : String(error)}`);
443
+ }
444
+ if (this.profile !== null) validateProfileName(this.profile, config);
445
+ if (this.model !== null) validateModelName(this.model, config);
446
+ const lowerLayers = await this.getLowerPrecedenceLayers();
447
+ const currentLayer = this.toPromptLayer();
448
+ const merged = mergePromptLayers(...lowerLayers, currentLayer);
449
+ if (currentLayer.model && !merged.ai.profile) throw new Error(`Prompt "${this.key}" model overrides require an effective prompt profile`);
450
+ }
451
+ async getLowerPrecedenceLayers() {
452
+ const definition = PromptRegistry.get(this.key);
453
+ const config = getPromptConfig$1();
454
+ const layers = [];
455
+ if (definition) layers.push({
456
+ template: definition.template,
457
+ profile: definition.ai.profile ?? null,
458
+ model: definition.ai.model ?? null,
459
+ params: definition.ai.params
460
+ });
461
+ layers.push(normalizePromptLayer(config.prompts?.[this.key]));
462
+ const { PromptOverrideCollection } = await Promise.resolve().then(() => PromptOverrideCollection_exports);
463
+ const collection = await PromptOverrideCollection.create({ db: this.options.db ?? this.options.persistence });
464
+ if (this.tenantId) {
465
+ const appOverride = await collection.getAppOverride(this.key, { excludeId: this.id ?? void 0 });
466
+ if (appOverride) layers.push(appOverride.toPromptLayer());
467
+ }
468
+ return layers;
469
+ }
470
+ normalizeParamsForPersistence() {
471
+ const rawParams = this.params;
472
+ if (rawParams === null || rawParams === void 0) {
473
+ this.params = null;
474
+ return;
475
+ }
476
+ if (typeof rawParams === "string") {
477
+ this.params = rawParams;
478
+ return;
479
+ }
480
+ if (typeof rawParams === "object" && !Array.isArray(rawParams)) {
481
+ this.params = serializePromptParams(rawParams);
482
+ return;
483
+ }
484
+ throw new Error(`Prompt "${this.key}" params must be a JSON object or JSON string`);
485
+ }
486
+ async getPersistedIdentity() {
487
+ if (!this.id) return null;
488
+ const existing = await this.db.get(this.tableName, { id: this.id });
489
+ if (!existing) return null;
490
+ return {
491
+ key: String(existing.key ?? this.key),
492
+ tenantId: existing.tenantId !== void 0 ? existing.tenantId : existing.tenant_id ?? null
493
+ };
494
+ }
495
+ };
496
+ __decorateClass$3([field({ required: true })], PromptOverride.prototype, "key", 2);
497
+ __decorateClass$3([field({
498
+ type: "text",
499
+ nullable: true
500
+ })], PromptOverride.prototype, "tenantId", 2);
501
+ __decorateClass$3([field({
502
+ type: "text",
503
+ nullable: true
504
+ })], PromptOverride.prototype, "template", 2);
505
+ __decorateClass$3([field({
506
+ type: "text",
507
+ nullable: true
508
+ })], PromptOverride.prototype, "profile", 2);
509
+ __decorateClass$3([field({
510
+ type: "text",
511
+ nullable: true
512
+ })], PromptOverride.prototype, "model", 2);
513
+ __decorateClass$3([field({
514
+ type: "text",
515
+ nullable: true
516
+ })], PromptOverride.prototype, "params", 2);
517
+ PromptOverride = __decorateClass$3([smrt({
518
+ tableName: "_smrt_prompt_overrides",
519
+ conflictColumns: ["key", "context"],
520
+ api: { include: [
521
+ "list",
522
+ "get",
523
+ "create",
524
+ "update",
525
+ "delete"
526
+ ] },
527
+ cli: {
528
+ include: [
529
+ "list",
530
+ "get",
531
+ "create",
532
+ "update",
533
+ "delete"
534
+ ],
535
+ exclude: [
536
+ "getParams",
537
+ "setParams",
538
+ "toPromptLayer"
539
+ ]
540
+ },
541
+ mcp: { include: [] }
542
+ })], PromptOverride);
543
+ var PromptOverrideCollection_exports = /* @__PURE__ */ __exportAll({ PromptOverrideCollection: () => PromptOverrideCollection });
544
+ var PromptOverrideCollection = class extends SmrtCollection {
545
+ static _itemClass = PromptOverride;
546
+ excludeOverrideId(items, excludeId) {
547
+ return items.filter((item) => excludeId ? item.id !== excludeId : true);
548
+ }
549
+ async getAppOverride(key, options = {}) {
550
+ const items = await this.list({ where: {
551
+ key,
552
+ tenantId: null
553
+ } });
554
+ return this.excludeOverrideId(items, options.excludeId)[0] ?? null;
555
+ }
556
+ async getTenantOverride(key, tenantId, options = {}) {
557
+ const items = await this.list({ where: {
558
+ key,
559
+ tenantId
560
+ } });
561
+ return this.excludeOverrideId(items, options.excludeId)[0] ?? null;
562
+ }
563
+ async getResolutionLayers(key, tenantId, options = {}) {
564
+ const [app, tenant] = await Promise.all([this.getAppOverride(key, options), tenantId != null ? this.getTenantOverride(key, tenantId, options) : Promise.resolve(null)]);
565
+ return {
566
+ app,
567
+ tenant
568
+ };
569
+ }
570
+ };
571
+ function getPromptConfig() {
572
+ return getPackageConfig("prompts", {
573
+ profiles: {},
574
+ prompts: {}
575
+ });
576
+ }
577
+ async function loadPromptBase(key, options) {
578
+ const definition = PromptRegistry.get(key);
579
+ if (!definition) throw new Error(`Unknown prompt key "${key}"`);
580
+ const tenantId = options.tenantId !== void 0 ? options.tenantId : getTenantId() ?? null;
581
+ let collection = null;
582
+ let cacheDb = options.db;
583
+ if (options.db) {
584
+ const initializedCollection = await PromptOverrideCollection.create({ db: options.db });
585
+ collection = initializedCollection;
586
+ cacheDb = initializedCollection.db;
587
+ }
588
+ const cached = getCachedPromptBase(key, tenantId, cacheDb);
589
+ if (cached) return cached;
590
+ const loadedAtGeneration = getPromptCacheGeneration(key, cacheDb);
591
+ const config = getPromptConfig();
592
+ const layers = [{
593
+ template: definition.template,
594
+ profile: definition.ai.profile ?? null,
595
+ model: definition.ai.model ?? null,
596
+ params: definition.ai.params
597
+ }, normalizePromptLayer(config.prompts?.[key])];
598
+ if (collection) {
599
+ const stored = await collection.getResolutionLayers(key, tenantId);
600
+ if (stored.app) layers.push(stored.app.toPromptLayer());
601
+ if (stored.tenant) layers.push(stored.tenant.toPromptLayer());
602
+ }
603
+ const merged = mergePromptLayers(...layers);
604
+ const value = {
605
+ key,
606
+ template: merged.template,
607
+ ai: merged.ai
608
+ };
609
+ setCachedPromptBase(key, tenantId, cacheDb, value, loadedAtGeneration);
610
+ return value;
611
+ }
612
+ async function resolvePrompt(key, options = {}) {
613
+ const config = getPromptConfig();
614
+ const base = await loadPromptBase(key, options);
615
+ const runtimeOverride = normalizePromptLayer(options.override);
616
+ const merged = mergePromptLayers({
617
+ template: base.template,
618
+ profile: base.ai.profile ?? null,
619
+ model: base.ai.model ?? null,
620
+ params: base.ai.params
621
+ }, runtimeOverride);
622
+ return {
623
+ key,
624
+ template: merged.template,
625
+ text: renderPromptTemplate(merged.template, options.variables),
626
+ ai: buildResolvedAI(merged.ai, config)
627
+ };
628
+ }
629
+ //#endregion
630
+ //#region ../profiles/dist/chunks/ProfileCollection-BvhD2cfL.js
631
+ var ProfileCollection_BvhD2cfL_exports = /* @__PURE__ */ __exportAll$1({
632
+ _: () => smrtProfilesGenerateBioPrompt,
633
+ a: () => Profile,
634
+ d: () => isOidcProvisioningRaceConflict,
635
+ f: () => saveOidcRaceArbiter,
636
+ g: () => promptMessageOptions,
637
+ h: () => ProfileType_exports,
638
+ i: () => OidcProfileEmailReservationCollection,
639
+ l: () => coordinateOidcProvisioning,
640
+ m: () => ProfileType,
641
+ n: () => ProfileCollection,
642
+ o: () => OidcProfileEmailReservation,
643
+ p: () => normalizeIdentityEmail,
644
+ r: () => ProfileCollection_exports,
645
+ s: () => PROFILE_EMAIL_KEY_BACKFILL_NAME,
646
+ t: () => CanonicalPersonProfileError,
647
+ u: () => isOidcAbortedTransactionError
648
+ });
649
+ var smrtProfilesGenerateBioPrompt = definePrompt({
650
+ key: "smrtProfiles.profile.generateBio",
651
+ template: `Write a short, professional bio for this person.
652
+
653
+ Profile name: {profileName}
654
+ Profile description: {profileDescription}
655
+
656
+ Return only the bio text, with no commentary or surrounding quotation marks.`,
657
+ editable: {
658
+ template: true,
659
+ profile: true,
660
+ model: true,
661
+ params: true
662
+ }
663
+ });
664
+ function promptMessageOptions(ai) {
665
+ return {
666
+ ...ai.params || {},
667
+ ...ai.model ? { model: ai.model } : {},
668
+ ...typeof ai.temperature === "number" ? { temperature: ai.temperature } : {},
669
+ ...typeof ai.maxTokens === "number" ? { maxTokens: ai.maxTokens } : {}
670
+ };
671
+ }
672
+ var ProfileType_exports = /* @__PURE__ */ __exportAll$2({ ProfileType: () => ProfileType });
673
+ var __defProp$2 = Object.defineProperty;
674
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
675
+ var __decorateClass$2 = (decorators, target, key, kind) => {
676
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
677
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
678
+ if (kind && result) __defProp$2(target, key, result);
679
+ return result;
680
+ };
681
+ var ProfileType = class extends SmrtObject {
682
+ tenantId = null;
683
+ name = "";
684
+ description;
685
+ constructor(options = {}) {
686
+ super(options);
687
+ if (options.name) this.name = options.name;
688
+ if (options.description !== void 0) this.description = options.description;
689
+ }
690
+ /**
691
+ * Convenience method for slug-based lookup
692
+ *
693
+ * @param slug - The slug to search for
694
+ * @returns ProfileType instance or null if not found
695
+ */
696
+ static async getBySlug(_slug) {
697
+ return null;
698
+ }
699
+ };
700
+ __decorateClass$2([tenantId({ nullable: true })], ProfileType.prototype, "tenantId", 2);
701
+ __decorateClass$2([field({ required: true })], ProfileType.prototype, "name", 2);
702
+ ProfileType = __decorateClass$2([TenantScoped({ mode: "optional" }), smrt({
703
+ tableStrategy: "sti",
704
+ api: { include: [
705
+ "list",
706
+ "get",
707
+ "create",
708
+ "update"
709
+ ] },
710
+ mcp: { include: ["list", "get"] },
711
+ cli: { skipApiCheck: true }
712
+ })], ProfileType);
713
+ function normalizeIdentityEmail(email) {
714
+ const normalized = email.trim().toLowerCase();
715
+ if (!normalized) throw new Error("Identity email normalization requires a non-blank email.");
716
+ return normalized;
717
+ }
718
+ var provisioningLocks = /* @__PURE__ */ new Map();
719
+ var sqliteRetryLocks = /* @__PURE__ */ new Map();
720
+ var savepointSequence = 0;
721
+ var OIDC_RACE_FIELD_NAMES = /* @__PURE__ */ new Set([
722
+ "email_key",
723
+ "emailKey",
724
+ "identity_key",
725
+ "identityKey",
726
+ "profile_id",
727
+ "profileId"
728
+ ]);
729
+ var OIDC_RACE_MESSAGE_PATTERNS = [
730
+ /oidc_identities[._].*identity_key/iu,
731
+ /oidc_identities_identity_key/iu,
732
+ /oidc_profile_email_reservations[._].*(?:email_key|profile_id)/iu,
733
+ /profile_types_slug_context_meta_type/iu
734
+ ];
735
+ var OidcRaceArbiterConflict = class extends Error {
736
+ constructor(cause) {
737
+ super("An OIDC provisioning race arbiter reported a unique conflict.", { cause });
738
+ this.name = "OidcRaceArbiterConflict";
739
+ }
740
+ };
741
+ var SavepointUnavailableError = class extends Error {
742
+ constructor(cause) {
743
+ super("OIDC provisioning could not start a transaction savepoint.", { cause });
744
+ this.name = "SavepointUnavailableError";
745
+ }
746
+ };
747
+ async function coordinateOidcProvisioning(options) {
748
+ const rootBackfillTableReady = typeof options.db.beginTransaction === "function";
749
+ return withProvisioningLocks(options.db, options.lockKeys, async () => {
750
+ if (rootBackfillTableReady) await new BackfillTracker({ db: options.db }).initialize();
751
+ const result = await retryProvisioning(options, () => withProvisioningTransaction(options.db, (tx) => {
752
+ if (rootBackfillTableReady) BackfillTracker.inheritInitialization(tx, options.db);
753
+ else BackfillTracker.requireExistingTable(tx);
754
+ return options.provision(tx);
755
+ }, options.createTransactionError));
756
+ return rootBackfillTableReady && options.rebindRootResult ? options.rebindRootResult(result, options.db) : result;
757
+ });
758
+ }
759
+ async function withProvisioningLocks(db, lockKeys, callback) {
760
+ const keys = [.../* @__PURE__ */ new Set([...!isPostgresDatabase(db) || typeof db.beginTransaction !== "function" ? ["adapter-transaction"] : [], ...lockKeys])].sort();
761
+ const acquire = (index) => index >= keys.length ? callback() : withProvisioningLock(db, keys[index], () => acquire(index + 1));
762
+ return acquire(0);
763
+ }
764
+ function isOidcAbortedTransactionError(error) {
765
+ return isAbortedTransactionError(error);
766
+ }
767
+ function testRaceMessagePattern(pattern, message) {
768
+ pattern.lastIndex = 0;
769
+ try {
770
+ return pattern.test(message);
771
+ } finally {
772
+ pattern.lastIndex = 0;
773
+ }
774
+ }
775
+ function isOidcProvisioningRaceConflict(error, options = {}) {
776
+ if (isOidcAbortedTransactionError(error)) return true;
777
+ const pending = [error];
778
+ const seen = /* @__PURE__ */ new Set();
779
+ const messagePatterns = [...OIDC_RACE_MESSAGE_PATTERNS, ...options.messagePatterns ?? []];
780
+ while (pending.length > 0) {
781
+ const current = pending.pop();
782
+ if (!current || typeof current !== "object" || seen.has(current)) continue;
783
+ seen.add(current);
784
+ if (current instanceof OidcRaceArbiterConflict) return true;
785
+ if (current instanceof Error && current.name === "CanonicalPersonProfileError" && current.code === "reservation_conflict") return true;
786
+ if (current instanceof ValidationError && current.code === "VALIDATION_UNIQUE_CONSTRAINT" && OIDC_RACE_FIELD_NAMES.has(String(current.details?.fieldName ?? ""))) return true;
787
+ if (current instanceof Error && messagePatterns.some((pattern) => testRaceMessagePattern(pattern, current.message))) return true;
788
+ const details = current;
789
+ pending.push(details.cause, details.context, details.details, details.originalError);
790
+ if (current instanceof AggregateError) pending.push(...current.errors);
791
+ }
792
+ return false;
793
+ }
794
+ async function saveOidcRaceArbiter(operation) {
795
+ try {
796
+ return await operation();
797
+ } catch (error) {
798
+ if (error instanceof ValidationError && error.code === "VALIDATION_UNIQUE_CONSTRAINT" || isOidcAbortedTransactionError(error)) throw new OidcRaceArbiterConflict(error);
799
+ throw error;
800
+ }
801
+ }
802
+ async function withProvisioningLock(db, lockKey, callback) {
803
+ const databaseKey = getDatabaseLockKey(db);
804
+ const locks = provisioningLocks.get(databaseKey) ?? /* @__PURE__ */ new Map();
805
+ provisioningLocks.set(databaseKey, locks);
806
+ const previous = locks.get(lockKey) ?? Promise.resolve();
807
+ let release;
808
+ const current = new Promise((resolve) => {
809
+ release = resolve;
810
+ });
811
+ const queued = previous.then(() => current);
812
+ locks.set(lockKey, queued);
813
+ await previous;
814
+ try {
815
+ return await callback();
816
+ } finally {
817
+ release();
818
+ if (locks.get(lockKey) === queued) locks.delete(lockKey);
819
+ if (locks.size === 0) provisioningLocks.delete(databaseKey);
820
+ }
821
+ }
822
+ function getDatabaseLockKey(db) {
823
+ const url = db.url;
824
+ if (isPostgresDatabase(db) && typeof db.beginTransaction !== "function" && db.client !== null && (typeof db.client === "object" || typeof db.client === "function")) return db.client;
825
+ return url && !isPostgresDatabase(db) ? url : db;
826
+ }
827
+ function isPostgresDatabase(db) {
828
+ return /^postgres(?:ql)?:/iu.test(db.url ?? "");
829
+ }
830
+ async function retryProvisioning(options, callback) {
831
+ let serializeSqliteRetry = false;
832
+ for (let attempt = 0; attempt < 8; attempt += 1) try {
833
+ return await (serializeSqliteRetry ? withSqliteRetryLock(options.db, callback) : callback());
834
+ } catch (error) {
835
+ if (error instanceof BackfillTableUnavailableError) {
836
+ if (typeof options.db.beginTransaction === "function" && attempt < 7) {
837
+ BackfillTracker.invalidateInitialization(options.db);
838
+ await new BackfillTracker({ db: options.db }).initialize();
839
+ continue;
840
+ }
841
+ throw options.createTransactionError("Transaction-bound OIDC provisioning requires an existing _smrt_backfills table; pass the root database so SMRT can initialize it safely.", error);
842
+ }
843
+ const sqliteBusy = isSqliteBusyError(error);
844
+ const postgresTransactionRetry = isPostgresTransactionRetryError(error);
845
+ const raceConflict = options.isRaceConflict(error);
846
+ if (!sqliteBusy && !postgresTransactionRetry && !raceConflict) throw error;
847
+ if (sqliteBusy && attempt < 7) {
848
+ serializeSqliteRetry = true;
849
+ await waitForRetry(Math.min(100 * 2 ** attempt, 1e3));
850
+ continue;
851
+ }
852
+ if (postgresTransactionRetry && attempt < 7) {
853
+ await waitForRetry(Math.min(25 * 2 ** attempt, 500));
854
+ continue;
855
+ }
856
+ if (raceConflict && attempt === 0) continue;
857
+ throw options.createConcurrencyError(error);
858
+ }
859
+ throw options.createConcurrencyError();
860
+ }
861
+ function isPostgresTransactionRetryError(error) {
862
+ const pending = [error];
863
+ const seen = /* @__PURE__ */ new Set();
864
+ const messagePattern = /deadlock detected|could not serialize access|serialization failure/iu;
865
+ while (pending.length > 0) {
866
+ const current = pending.pop();
867
+ if (!current || typeof current !== "object" || seen.has(current)) continue;
868
+ seen.add(current);
869
+ const details = current;
870
+ if (details.code === "40P01" || details.code === "40001") return true;
871
+ if (current instanceof Error && messagePattern.test(current.message)) return true;
872
+ pending.push(details.cause, details.context, details.originalError);
873
+ if (current instanceof AggregateError) pending.push(...current.errors);
874
+ }
875
+ return false;
876
+ }
877
+ async function withSqliteRetryLock(db, callback) {
878
+ const key = db.url || db;
879
+ const previous = sqliteRetryLocks.get(key) ?? Promise.resolve();
880
+ let release;
881
+ const current = new Promise((resolve) => {
882
+ release = resolve;
883
+ });
884
+ const queued = previous.then(() => current);
885
+ sqliteRetryLocks.set(key, queued);
886
+ await previous;
887
+ try {
888
+ return await callback();
889
+ } finally {
890
+ release();
891
+ if (sqliteRetryLocks.get(key) === queued) sqliteRetryLocks.delete(key);
892
+ }
893
+ }
894
+ function isSqliteBusyError(error) {
895
+ const pending = [error];
896
+ const seen = /* @__PURE__ */ new Set();
897
+ const busyPattern = /SQLITE_(?:BUSY|LOCKED)|database(?: table)? is locked|cannot (?:commit transaction|release savepoint) - SQL statements in progress/iu;
898
+ while (pending.length > 0) {
899
+ const current = pending.pop();
900
+ if (typeof current === "string") {
901
+ if (busyPattern.test(current)) return true;
902
+ continue;
903
+ }
904
+ if (!current || typeof current !== "object" || seen.has(current)) continue;
905
+ seen.add(current);
906
+ if (current instanceof Error) {
907
+ if (busyPattern.test(current.message)) return true;
908
+ pending.push(current.cause);
909
+ if (current instanceof AggregateError) pending.push(...current.errors);
910
+ }
911
+ const details = current;
912
+ pending.push(details.context, details.details, details.originalError);
913
+ }
914
+ return false;
915
+ }
916
+ async function waitForRetry(milliseconds) {
917
+ await new Promise((resolve) => setTimeout(resolve, milliseconds));
918
+ }
919
+ async function withProvisioningTransaction(db, callback, createTransactionError) {
920
+ if (typeof db.beginTransaction === "function") {
921
+ if (db.transaction) return db.transaction((tx2) => callback(tx2));
922
+ const tx = await db.beginTransaction();
923
+ try {
924
+ const result = await callback(tx);
925
+ await tx.commit();
926
+ return result;
927
+ } catch (error) {
928
+ try {
929
+ if (tx.isActive()) await tx.rollback();
930
+ } catch (rollbackError) {
931
+ throw createTransactionError("OIDC provisioning could not roll back its root transaction.", new AggregateError([error, rollbackError]));
932
+ }
933
+ throw error;
934
+ }
935
+ }
936
+ try {
937
+ return await withSavepoint(db, () => callback(db), createTransactionError);
938
+ } catch (error) {
939
+ if (!(error instanceof SavepointUnavailableError)) throw error;
940
+ throw createTransactionError("OIDC provisioning requires a root database or a transaction handle that supports savepoints.", error.cause);
941
+ }
942
+ }
943
+ async function withSavepoint(db, callback, createTransactionError) {
944
+ savepointSequence = (savepointSequence + 1) % Number.MAX_SAFE_INTEGER;
945
+ const savepoint = `smrt_oidc_${savepointSequence.toString(36)}`;
946
+ try {
947
+ await db.query(`SAVEPOINT ${savepoint}`);
948
+ } catch (error) {
949
+ throw new SavepointUnavailableError(error);
950
+ }
951
+ try {
952
+ const result = await callback();
953
+ await db.query(`RELEASE SAVEPOINT ${savepoint}`);
954
+ return result;
955
+ } catch (error) {
956
+ try {
957
+ await db.query(`ROLLBACK TO SAVEPOINT ${savepoint}`);
958
+ await db.query(`RELEASE SAVEPOINT ${savepoint}`);
959
+ } catch (rollbackError) {
960
+ throw createTransactionError("OIDC provisioning could not roll back its transaction savepoint.", new AggregateError([error, rollbackError]));
961
+ }
962
+ throw error;
963
+ }
964
+ }
965
+ var __defProp$1 = Object.defineProperty;
966
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
967
+ var __decorateClass$1 = (decorators, target, key, kind) => {
968
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
969
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
970
+ if (kind && result) __defProp$1(target, key, result);
971
+ return result;
972
+ };
973
+ var OidcProfileEmailReservation = class extends SmrtObject {
974
+ profileId;
975
+ emailKey = "";
976
+ constructor(options = {}) {
977
+ super(options);
978
+ if (options.profileId !== void 0) this.profileId = options.profileId;
979
+ if (options.emailKey !== void 0) this.emailKey = options.emailKey;
980
+ }
981
+ };
982
+ __decorateClass$1([foreignKey("Profile", {
983
+ required: true,
984
+ unique: true
985
+ })], OidcProfileEmailReservation.prototype, "profileId", 2);
986
+ __decorateClass$1([field({
987
+ type: "text",
988
+ required: true,
989
+ unique: true,
990
+ readonly: true
991
+ })], OidcProfileEmailReservation.prototype, "emailKey", 2);
992
+ OidcProfileEmailReservation = __decorateClass$1([smrt({
993
+ tableName: "oidc_profile_email_reservations",
994
+ api: false,
995
+ mcp: false,
996
+ cli: false
997
+ })], OidcProfileEmailReservation);
998
+ var __defProp = Object.defineProperty;
999
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
1000
+ var __decorateClass = (decorators, target, key, kind) => {
1001
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
1002
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
1003
+ if (kind && result) __defProp(target, key, result);
1004
+ return result;
1005
+ };
1006
+ var Profile = class extends SmrtObject {
1007
+ tenantId = null;
1008
+ typeId;
1009
+ email;
1010
+ emailKey = null;
1011
+ name = "";
1012
+ description;
1013
+ metadata = [];
1014
+ relationshipsFrom = [];
1015
+ relationshipsTo = [];
1016
+ constructor(options = {}) {
1017
+ super(options);
1018
+ if (options.typeId !== void 0) this.typeId = options.typeId;
1019
+ if (options.email !== void 0) this.email = options.email;
1020
+ if (options.name) this.name = options.name;
1021
+ if (options.description !== void 0) this.description = options.description;
1022
+ }
1023
+ /** Keep the durable identity key derived from the public email field. */
1024
+ async save() {
1025
+ this.emailKey = this.email?.trim() ? normalizeIdentityEmail(this.email) : null;
1026
+ return super.save();
1027
+ }
1028
+ /**
1029
+ * Get the profile type slug for this profile
1030
+ *
1031
+ * @returns The slug of the profile type
1032
+ */
1033
+ async getTypeSlug() {
1034
+ return (await this.loadRelated("typeId"))?.slug || "";
1035
+ }
1036
+ /**
1037
+ * Set the profile type by slug
1038
+ *
1039
+ * @param slug - The slug of the profile type
1040
+ * @throws Error if profile type not found
1041
+ */
1042
+ async setTypeBySlug(slug) {
1043
+ const type = await ProfileType.getBySlug(slug);
1044
+ if (!type) throw new Error(`Profile type '${slug}' not found`);
1045
+ this.typeId = type.id;
1046
+ }
1047
+ /**
1048
+ * Add metadata to this profile
1049
+ *
1050
+ * @param metafieldSlug - The slug of the metafield
1051
+ * @param value - The value to set
1052
+ */
1053
+ async addMetadata(metafieldSlug, value) {
1054
+ const { ProfileMetafieldCollection } = await import("./ProfileMetafieldCollection-CMKhVi2I--ba-T4qA.js").then((n) => n.n);
1055
+ const { ProfileMetadataCollection } = await import("./ProfileMetadataCollection-DxX_BJKV-StatXt-E.js").then((n) => n.n);
1056
+ const metafield = await (await ProfileMetafieldCollection.create(this.options)).getBySlug(metafieldSlug);
1057
+ if (!metafield) throw new Error(`Metafield '${metafieldSlug}' not found`);
1058
+ await metafield.validateValue(value);
1059
+ const metadataCollection = await ProfileMetadataCollection.create(this.options);
1060
+ const existing = await metadataCollection.list({
1061
+ where: {
1062
+ profileId: this.id,
1063
+ metafieldId: metafield.id
1064
+ },
1065
+ limit: 1
1066
+ });
1067
+ if (existing.length > 0) {
1068
+ const metadata = existing[0];
1069
+ metadata.value = String(value);
1070
+ await metadata.save();
1071
+ } else {
1072
+ if (!this.id || !metafield.id) throw new Error("Profile.addMetadata requires a persisted profile and metafield (missing id)");
1073
+ await (await metadataCollection.create({
1074
+ profileId: this.id,
1075
+ metafieldId: metafield.id,
1076
+ value: String(value)
1077
+ })).save();
1078
+ }
1079
+ }
1080
+ /**
1081
+ * Get all metadata for this profile as key-value object
1082
+ *
1083
+ * @returns Object with metafield slugs as keys
1084
+ */
1085
+ async getMetadata() {
1086
+ const { ProfileMetadataCollection } = await import("./ProfileMetadataCollection-DxX_BJKV-StatXt-E.js").then((n) => n.n);
1087
+ return await (await ProfileMetadataCollection.create(this.options)).getMetadataObject(this.id);
1088
+ }
1089
+ /**
1090
+ * Update multiple metadata values
1091
+ *
1092
+ * @param metadata - Object with metafield slugs as keys and values
1093
+ */
1094
+ async updateMetadata(metadata) {
1095
+ for (const [metafieldSlug, value] of Object.entries(metadata)) await this.addMetadata(metafieldSlug, value);
1096
+ }
1097
+ /**
1098
+ * Remove metadata by metafield slug
1099
+ *
1100
+ * @param metafieldSlug - The slug of the metafield to remove
1101
+ */
1102
+ async removeMetadata(metafieldSlug) {
1103
+ const { ProfileMetafieldCollection } = await import("./ProfileMetafieldCollection-CMKhVi2I--ba-T4qA.js").then((n) => n.n);
1104
+ const { ProfileMetadataCollection } = await import("./ProfileMetadataCollection-DxX_BJKV-StatXt-E.js").then((n) => n.n);
1105
+ const metafield = await (await ProfileMetafieldCollection.create(this.options)).getBySlug(metafieldSlug);
1106
+ if (!metafield) throw new Error(`Metafield '${metafieldSlug}' not found`);
1107
+ const existing = await (await ProfileMetadataCollection.create(this.options)).list({
1108
+ where: {
1109
+ profileId: this.id,
1110
+ metafieldId: metafield.id
1111
+ },
1112
+ limit: 1
1113
+ });
1114
+ if (existing.length > 0) await existing[0].delete();
1115
+ }
1116
+ async getProfileAssetCollection() {
1117
+ const { ProfileAssetCollection } = await import("./ProfileAssetCollection-V8u4wqSL-BKz6v8py.js").then((n) => n.n);
1118
+ return ProfileAssetCollection.create({ db: this.db });
1119
+ }
1120
+ async getAssets(relationship) {
1121
+ if (!this.id) return [];
1122
+ const linkedAssets = await (await this.getProfileAssetCollection()).byLeft(this.id, relationship ? { relationship } : {});
1123
+ return resolveOwnedAssetsById(this.db, linkedAssets.map((link) => link.assetId), this.tenantId);
1124
+ }
1125
+ async addAsset(asset, relationship = "attachment", sortOrder = 0) {
1126
+ if (!this.id || !asset.id) throw new Error("Cannot associate unsaved profile or asset");
1127
+ assertValidOwnedAssetRelationship(relationship);
1128
+ assertValidOwnedAssetSortOrder(sortOrder);
1129
+ await (await this.getProfileAssetCollection()).attach(this.id, asset.id, {
1130
+ relationship,
1131
+ sortOrder,
1132
+ tenantId: this.tenantId
1133
+ });
1134
+ }
1135
+ async removeAsset(assetId, relationship) {
1136
+ if (!this.id) return;
1137
+ await (await this.getProfileAssetCollection()).detach(this.id, assetId, relationship ? { relationship } : {});
1138
+ }
1139
+ /**
1140
+ * Add a relationship to another profile
1141
+ *
1142
+ * @param toProfile - The target profile
1143
+ * @param relationshipSlug - The type of relationship
1144
+ * @param contextProfile - Optional context profile for tertiary relationships
1145
+ */
1146
+ async addRelationship(toProfile, relationshipSlug, contextProfile) {
1147
+ const { ProfileRelationshipTypeCollection } = await import("./ProfileRelationshipTypeCollection-BdXwd7Kf-GEu0alx6.js").then((n) => n.n);
1148
+ const { ProfileRelationshipCollection } = await import("./ProfileRelationshipCollection-DUVgaIqf-B4TDc4TQ.js").then((n) => n.n);
1149
+ const { ProfileRelationshipType } = await import("./ProfileRelationshipType-BRiRka6t-Dw1efEX5.js").then((n) => n.n).then((n) => n.n);
1150
+ const relationshipType = await (await ProfileRelationshipTypeCollection.create(this.options)).getBySlug(relationshipSlug);
1151
+ if (!relationshipType) throw new Error(`Relationship type '${relationshipSlug}' not found`);
1152
+ const relationshipCollection = await ProfileRelationshipCollection.create(this.options);
1153
+ if (!await relationshipCollection.exists(this.id, toProfile.id, relationshipType.id)) {
1154
+ await (await relationshipCollection.create({
1155
+ fromProfileId: this.id ?? void 0,
1156
+ toProfileId: toProfile.id ?? void 0,
1157
+ typeId: relationshipType.id ?? void 0,
1158
+ contextProfileId: contextProfile?.id ?? void 0
1159
+ })).save();
1160
+ if (relationshipType.reciprocal) {
1161
+ const handler = ProfileRelationshipType.getReciprocalHandler(relationshipSlug);
1162
+ if (handler) await handler(this, toProfile, contextProfile);
1163
+ }
1164
+ }
1165
+ }
1166
+ /**
1167
+ * Get all relationships for this profile
1168
+ *
1169
+ * @param options - Filter options (typeSlug, direction)
1170
+ * @returns Array of ProfileRelationship instances
1171
+ */
1172
+ async getRelationships(options) {
1173
+ const { ProfileRelationshipCollection } = await import("./ProfileRelationshipCollection-DUVgaIqf-B4TDc4TQ.js").then((n) => n.n);
1174
+ const { ProfileRelationshipTypeCollection } = await import("./ProfileRelationshipTypeCollection-BdXwd7Kf-GEu0alx6.js").then((n) => n.n);
1175
+ const relationshipCollection = await ProfileRelationshipCollection.create(this.options);
1176
+ const direction = options?.direction || "all";
1177
+ let typeId;
1178
+ if (options?.typeSlug) typeId = (await (await ProfileRelationshipTypeCollection.create(this.options)).getBySlug(options.typeSlug))?.id ?? void 0;
1179
+ if (direction === "from") return await relationshipCollection.getFromProfile(this.id, typeId);
1180
+ else if (direction === "to") return await relationshipCollection.getToProfile(this.id, typeId);
1181
+ else return await relationshipCollection.getForProfile(this.id, typeId);
1182
+ }
1183
+ /**
1184
+ * Get related profiles
1185
+ *
1186
+ * @param relationshipSlug - Optional filter by relationship type slug
1187
+ * @returns Array of related Profile instances
1188
+ */
1189
+ async getRelatedProfiles(relationshipSlug) {
1190
+ const { ProfileCollection } = await Promise.resolve().then(() => ProfileCollection_exports);
1191
+ const relationships = await this.getRelationships({
1192
+ typeSlug: relationshipSlug,
1193
+ direction: "all"
1194
+ });
1195
+ const profileCollection = await ProfileCollection.create(this.options);
1196
+ const relatedProfiles = [];
1197
+ const seenIds = /* @__PURE__ */ new Set();
1198
+ for (const relationship of relationships) {
1199
+ const fromId = String(relationship.fromProfileId);
1200
+ const toId = String(relationship.toProfileId);
1201
+ const otherId = fromId === String(this.id) ? toId : fromId;
1202
+ if (!seenIds.has(otherId)) {
1203
+ seenIds.add(otherId);
1204
+ const profile = await profileCollection.get({ id: otherId });
1205
+ if (profile) relatedProfiles.push(profile);
1206
+ }
1207
+ }
1208
+ return relatedProfiles;
1209
+ }
1210
+ /**
1211
+ * Remove a relationship to another profile
1212
+ *
1213
+ * @param toProfile - The target profile
1214
+ * @param relationshipSlug - The type of relationship to remove
1215
+ */
1216
+ async removeRelationship(toProfile, relationshipSlug) {
1217
+ const { ProfileRelationshipTypeCollection } = await import("./ProfileRelationshipTypeCollection-BdXwd7Kf-GEu0alx6.js").then((n) => n.n);
1218
+ const { ProfileRelationshipCollection } = await import("./ProfileRelationshipCollection-DUVgaIqf-B4TDc4TQ.js").then((n) => n.n);
1219
+ const relationshipType = await (await ProfileRelationshipTypeCollection.create(this.options)).getBySlug(relationshipSlug);
1220
+ if (!relationshipType) throw new Error(`Relationship type '${relationshipSlug}' not found`);
1221
+ const relationshipCollection = await ProfileRelationshipCollection.create(this.options);
1222
+ const relationships = await relationshipCollection.list({ where: {
1223
+ fromProfileId: this.id,
1224
+ toProfileId: toProfile.id,
1225
+ typeId: relationshipType.id
1226
+ } });
1227
+ for (const relationship of relationships) await relationship.delete();
1228
+ if (relationshipType.reciprocal) {
1229
+ const inverseRelationships = await relationshipCollection.list({ where: {
1230
+ fromProfileId: toProfile.id,
1231
+ toProfileId: this.id,
1232
+ typeId: relationshipType.id
1233
+ } });
1234
+ for (const relationship of inverseRelationships) await relationship.delete();
1235
+ }
1236
+ }
1237
+ /**
1238
+ * AI-powered: Generate a professional bio for this profile
1239
+ *
1240
+ * Uses the `smrtProfiles.profile.generateBio` prompt registered via
1241
+ * `@happyvertical/smrt-prompts`, allowing tenant- or instance-level
1242
+ * overrides of the template, model, and parameters at runtime.
1243
+ *
1244
+ * @returns Generated bio text
1245
+ */
1246
+ async generateBio() {
1247
+ const db = this.options.db ?? this.options.persistence;
1248
+ const resolvedPrompt = await resolvePrompt(smrtProfilesGenerateBioPrompt.key, {
1249
+ db,
1250
+ tenantId: this.tenantId,
1251
+ variables: {
1252
+ profileName: this.name || "",
1253
+ profileDescription: this.description || ""
1254
+ }
1255
+ });
1256
+ return (await (await this.getAiClient()).message(resolvedPrompt.text, promptMessageOptions(resolvedPrompt.ai))).trim();
1257
+ }
1258
+ /**
1259
+ * AI-powered: Check if profile matches criteria
1260
+ *
1261
+ * @param criteria - Criteria to match against
1262
+ * @returns True if matches criteria
1263
+ */
1264
+ async matches(criteria) {
1265
+ return await this.is(criteria);
1266
+ }
1267
+ /**
1268
+ * Find profiles by metadata key-value pair
1269
+ *
1270
+ * @param metafieldSlug - The metafield slug to search
1271
+ * @param value - The value to match
1272
+ * @returns Array of matching profiles
1273
+ */
1274
+ static async findByMetadata(_metafieldSlug, _value) {
1275
+ return [];
1276
+ }
1277
+ /**
1278
+ * Find profiles by type slug
1279
+ *
1280
+ * @param typeSlug - The profile type slug
1281
+ * @returns Array of matching profiles
1282
+ */
1283
+ static async findByType(_typeSlug) {
1284
+ return [];
1285
+ }
1286
+ /**
1287
+ * Find related profiles for a given profile
1288
+ *
1289
+ * @param profileId - The profile UUID
1290
+ * @param relationshipSlug - Optional filter by relationship type
1291
+ * @returns Array of related profiles
1292
+ */
1293
+ static async findRelated(_profileId, _relationshipSlug) {
1294
+ return [];
1295
+ }
1296
+ /**
1297
+ * Search profiles by email
1298
+ *
1299
+ * @param email - The email to search for
1300
+ * @returns Profile or null if not found
1301
+ */
1302
+ static async searchByEmail(_email) {
1303
+ return null;
1304
+ }
1305
+ /**
1306
+ * Get all API keys for this profile
1307
+ *
1308
+ * @returns Array of API keys
1309
+ */
1310
+ async getApiKeys() {
1311
+ const { ApiKeyCollection } = await import("./ApiKeyCollection-Bfb_nw81-Dks_0546.js").then((n) => n.n);
1312
+ return await (await ApiKeyCollection.create(this.options)).findByProfile(this.id);
1313
+ }
1314
+ /**
1315
+ * Get active (non-revoked, non-expired) API keys for this profile
1316
+ *
1317
+ * @returns Array of active API keys
1318
+ */
1319
+ async getActiveApiKeys() {
1320
+ const { ApiKeyCollection } = await import("./ApiKeyCollection-Bfb_nw81-Dks_0546.js").then((n) => n.n);
1321
+ return await (await ApiKeyCollection.create(this.options)).findActiveByProfile(this.id);
1322
+ }
1323
+ /**
1324
+ * Generate a new API key for this profile
1325
+ *
1326
+ * @param options - Key options (name, scopes, expiration)
1327
+ * @returns The generated key (plaintext) and ApiKey record
1328
+ */
1329
+ async generateApiKey(options) {
1330
+ const { ApiKey } = await import("./ApiKey-BZmbOTnL-Dz-PO2sa.js").then((n) => n.n).then((n) => n.n);
1331
+ return await ApiKey.generate(this, {
1332
+ ...options,
1333
+ db: this.options?.db
1334
+ });
1335
+ }
1336
+ /**
1337
+ * Get all OIDC identities linked to this profile
1338
+ *
1339
+ * @returns Array of OIDC identity records
1340
+ */
1341
+ async getOidcIdentities() {
1342
+ const { OidcIdentityCollection } = await import("./OidcIdentityCollection-D3L0WYbM-C85jz9CB.js").then((n) => n.r);
1343
+ return await (await OidcIdentityCollection.create(this.options)).findByProfile(this.id);
1344
+ }
1345
+ /**
1346
+ * Reuse an existing exact OIDC identity for this unchanged Profile.
1347
+ *
1348
+ * @param oidcData - OIDC provider data
1349
+ * @returns The linked OIDC identity record
1350
+ * @deprecated New authentication links require owner-aware transactional
1351
+ * provisioning. This compatibility helper may refresh a legacy Profile
1352
+ * type, but refuses to create or rebind authority.
1353
+ */
1354
+ async linkOidcIdentity(oidcData) {
1355
+ const { OidcIdentityCollection } = await import("./OidcIdentityCollection-D3L0WYbM-C85jz9CB.js").then((n) => n.r);
1356
+ return await (await OidcIdentityCollection.create(this.options)).linkToProfile(this, oidcData);
1357
+ }
1358
+ /**
1359
+ * Get all Nostr identities linked to this profile
1360
+ *
1361
+ * @returns Array of Nostr identity records
1362
+ */
1363
+ async getNostrIdentities() {
1364
+ const { NostrIdentityCollection } = await import("./NostrIdentityCollection-Ca74dkaL-DESXyrjY.js").then((n) => n.n);
1365
+ return await (await NostrIdentityCollection.create(this.options)).findByProfile(this.id);
1366
+ }
1367
+ /**
1368
+ * Link a new Nostr identity to this profile
1369
+ *
1370
+ * @param nostrData - Nostr identity data (encrypted keypair)
1371
+ * @returns The linked Nostr identity record
1372
+ */
1373
+ async linkNostrIdentity(nostrData) {
1374
+ const { NostrIdentityCollection } = await import("./NostrIdentityCollection-Ca74dkaL-DESXyrjY.js").then((n) => n.n);
1375
+ return await (await NostrIdentityCollection.create(this.options)).linkToProfile(this, nostrData);
1376
+ }
1377
+ /**
1378
+ * Get audit logs for actions performed by this profile
1379
+ *
1380
+ * @param limit - Maximum number of logs to return
1381
+ * @returns Array of audit log entries
1382
+ */
1383
+ async getAuditLogs(limit = 50) {
1384
+ const { AuditLogCollection } = await import("./AuditLogCollection-B1YonCAP-Db9tLDtf.js").then((n) => n.n);
1385
+ return await (await AuditLogCollection.create(this.options)).getRecentActivity(this.id, limit);
1386
+ }
1387
+ /**
1388
+ * Record an audit log entry for an action by this profile
1389
+ *
1390
+ * @param options - Audit log options
1391
+ * @returns The created audit log entry
1392
+ */
1393
+ async recordAction(options) {
1394
+ const { AuditLogCollection } = await import("./AuditLogCollection-B1YonCAP-Db9tLDtf.js").then((n) => n.n);
1395
+ return await (await AuditLogCollection.create(this.options)).record({
1396
+ profile: this,
1397
+ ...options
1398
+ });
1399
+ }
1400
+ };
1401
+ __decorateClass([tenantId({ nullable: true })], Profile.prototype, "tenantId", 2);
1402
+ __decorateClass([foreignKey("ProfileType", { required: true })], Profile.prototype, "typeId", 2);
1403
+ __decorateClass([field({
1404
+ type: "text",
1405
+ nullable: true,
1406
+ indexed: true,
1407
+ readonly: true
1408
+ })], Profile.prototype, "emailKey", 2);
1409
+ __decorateClass([field({ required: true })], Profile.prototype, "name", 2);
1410
+ __decorateClass([oneToMany("ProfileMetadata")], Profile.prototype, "metadata", 2);
1411
+ __decorateClass([oneToMany("ProfileRelationship", { foreignKey: "fromProfileId" })], Profile.prototype, "relationshipsFrom", 2);
1412
+ __decorateClass([oneToMany("ProfileRelationship", { foreignKey: "toProfileId" })], Profile.prototype, "relationshipsTo", 2);
1413
+ Profile = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
1414
+ tableStrategy: "sti",
1415
+ api: { include: [
1416
+ "list",
1417
+ "get",
1418
+ "create",
1419
+ "update",
1420
+ "delete"
1421
+ ] },
1422
+ mcp: { include: [
1423
+ "list",
1424
+ "get",
1425
+ "create",
1426
+ "update"
1427
+ ] },
1428
+ cli: { skipApiCheck: true }
1429
+ })], Profile);
1430
+ var OidcProfileEmailReservationCollection = class extends SmrtCollection {
1431
+ static _itemClass = OidcProfileEmailReservation;
1432
+ };
1433
+ var ProfileCollection_exports = /* @__PURE__ */ __exportAll$2({
1434
+ CanonicalPersonProfileError: () => CanonicalPersonProfileError,
1435
+ ProfileCollection: () => ProfileCollection
1436
+ });
1437
+ var PERSON_META_TYPES = /* @__PURE__ */ new Set(["@happyvertical/smrt-profiles:Person", "Person"]);
1438
+ var CanonicalPersonProfileError = class extends Error {
1439
+ constructor(code, message) {
1440
+ super(message);
1441
+ this.code = code;
1442
+ this.name = "CanonicalPersonProfileError";
1443
+ }
1444
+ code;
1445
+ };
1446
+ var ProfileCollection = class extends SmrtCollection {
1447
+ static _itemClass = Profile;
1448
+ emailKeysReadyPromise = null;
1449
+ /**
1450
+ * Find a profile by email address
1451
+ *
1452
+ * @param email - The email address to search for
1453
+ * @returns The matching profile or null
1454
+ */
1455
+ async findByEmail(email) {
1456
+ const normalizedEmail = normalizeIdentityEmail(email);
1457
+ return (await this.list({
1458
+ where: { email: normalizedEmail },
1459
+ limit: 1
1460
+ }))[0] ?? null;
1461
+ }
1462
+ /**
1463
+ * Resolve one unambiguous global Person by normalized email.
1464
+ *
1465
+ * Unlike `findByEmail()`, this identity-boundary helper deliberately reads
1466
+ * across tenant scopes and fails closed when any matching row is
1467
+ * tenant-scoped, is not a Person STI row, or when more than one row matches
1468
+ * case-insensitively. It is intended for verified external identities.
1469
+ */
1470
+ async findUniqueGlobalPersonByEmail(email) {
1471
+ const normalizedEmail = normalizeIdentityEmail(email);
1472
+ const rows = await this.loadCanonicalEmailRows(normalizedEmail);
1473
+ if (rows.length === 0) return null;
1474
+ this.assertCanonicalRows(rows, normalizedEmail);
1475
+ return this.requireHydratedProfile(readRequiredString(rows[0], "id"));
1476
+ }
1477
+ /**
1478
+ * Validate and hydrate a supplied canonical Profile.
1479
+ *
1480
+ * The Profile must be the sole case-insensitive match for its stored email.
1481
+ * When `email` is provided, that address must also match the stored email.
1482
+ */
1483
+ async requireCanonicalGlobalPerson(profileId, email) {
1484
+ const db = this.requireDatabase();
1485
+ await this.ensureEmailKeysReady();
1486
+ const row = (await withSystemContext(() => db.query(`SELECT CAST(id AS VARCHAR) AS id,
1487
+ tenant_id, _meta_type, email, email_key
1488
+ FROM profiles
1489
+ WHERE id = ?
1490
+ LIMIT 1`, profileId))).rows[0];
1491
+ if (!row) throw new CanonicalPersonProfileError("missing_profile", `Profile ${profileId} does not exist.`);
1492
+ this.assertRowEmailKeyCurrent(row);
1493
+ this.assertCanonicalRows([row]);
1494
+ const storedEmail = readString(row, "email");
1495
+ const normalizedEmail = email !== void 0 ? normalizeIdentityEmail(email) : storedEmail ? normalizeIdentityEmail(storedEmail) : void 0;
1496
+ if (normalizedEmail !== void 0) {
1497
+ const matches = await this.loadCanonicalEmailRows(normalizedEmail);
1498
+ this.assertCanonicalRows(matches, normalizedEmail);
1499
+ if (matches.length !== 1 || readRequiredString(matches[0], "id") !== profileId) throw new CanonicalPersonProfileError("email_mismatch", `Profile ${profileId} is not the unique global Person for ${normalizedEmail}.`);
1500
+ }
1501
+ return this.requireHydratedProfile(profileId);
1502
+ }
1503
+ /**
1504
+ * Reserve the normalized email for a previously validated canonical Person.
1505
+ *
1506
+ * The unique stored key turns a concurrent OIDC first-login race into a
1507
+ * retryable database conflict. Legacy Profiles remain unaffected until an
1508
+ * identity boundary safely claims them.
1509
+ */
1510
+ async reserveCanonicalIdentityEmail(profileId, email) {
1511
+ const profile = await this.requireCanonicalGlobalPerson(profileId, email);
1512
+ const normalizedEmail = email !== void 0 ? normalizeIdentityEmail(email) : profile.email?.trim() ? normalizeIdentityEmail(profile.email) : null;
1513
+ const db = this.requireDatabase();
1514
+ const reservations = await OidcProfileEmailReservationCollection.create({ db });
1515
+ const byProfile = await reservations.list({
1516
+ where: { profileId },
1517
+ limit: 2
1518
+ });
1519
+ if (byProfile.length > 1) throw new CanonicalPersonProfileError("ambiguous_email", `Multiple canonical identity reservations exist for Profile ${profileId}.`);
1520
+ if (normalizedEmail === null) {
1521
+ await byProfile[0]?.delete();
1522
+ return profile;
1523
+ }
1524
+ const byEmail = await reservations.list({
1525
+ where: { emailKey: normalizedEmail },
1526
+ limit: 2
1527
+ });
1528
+ const activeEmailReservations = [];
1529
+ for (const reservation of byEmail) if (reservation.profileId !== profileId && await this.isStaleEmailReservation(reservation, normalizedEmail)) await reservation.delete();
1530
+ else activeEmailReservations.push(reservation);
1531
+ if (activeEmailReservations.find((reservation) => reservation.profileId !== profileId)) throw new CanonicalPersonProfileError("reservation_conflict", `${normalizedEmail} is already reserved for a different canonical Person.`);
1532
+ if (activeEmailReservations.length > 1) throw new CanonicalPersonProfileError("ambiguous_email", `Multiple canonical identity reservations exist for ${normalizedEmail}.`);
1533
+ const existing = byProfile[0];
1534
+ if (existing) {
1535
+ if (existing.emailKey !== normalizedEmail) {
1536
+ existing.emailKey = normalizedEmail;
1537
+ await saveIdentityEmailReservation(existing, normalizedEmail);
1538
+ }
1539
+ return profile;
1540
+ }
1541
+ if (!activeEmailReservations[0]) {
1542
+ const reservation = new OidcProfileEmailReservation({
1543
+ db,
1544
+ emailKey: normalizedEmail,
1545
+ profileId
1546
+ });
1547
+ await reservation.initialize();
1548
+ await saveIdentityEmailReservation(reservation, normalizedEmail);
1549
+ }
1550
+ return profile;
1551
+ }
1552
+ /**
1553
+ * Find profiles by type slug
1554
+ *
1555
+ * @param typeSlug - The profile type slug to filter by
1556
+ * @returns Array of matching profiles
1557
+ */
1558
+ async findByType(typeSlug) {
1559
+ const allProfiles = await this.list({});
1560
+ const filtered = [];
1561
+ for (const profile of allProfiles) if (await profile.getTypeSlug() === typeSlug) filtered.push(profile);
1562
+ return filtered;
1563
+ }
1564
+ requireDatabase() {
1565
+ const db = this.options.db;
1566
+ if (!db || typeof db !== "object" || !("query" in db)) throw new Error("Canonical Profile identity resolution requires an initialized database.");
1567
+ return db;
1568
+ }
1569
+ async loadCanonicalEmailRows(normalizedEmail) {
1570
+ const db = this.requireDatabase();
1571
+ await this.ensureEmailKeysReady();
1572
+ const rows = (await withSystemContext(async () => {
1573
+ return db.query(`SELECT CAST(id AS VARCHAR) AS id,
1574
+ tenant_id, _meta_type, email, email_key
1575
+ FROM profiles
1576
+ WHERE email_key = ?
1577
+ ORDER BY created_at ASC, id ASC
1578
+ LIMIT 2`, normalizedEmail);
1579
+ })).rows;
1580
+ for (const row of rows) this.assertRowEmailKeyCurrent(row, normalizedEmail);
1581
+ return rows;
1582
+ }
1583
+ /** Require the deploy-time backfill marker before indexed identity reads. */
1584
+ async ensureEmailKeysReady() {
1585
+ if (!this.emailKeysReadyPromise) this.emailKeysReadyPromise = this.checkEmailKeysReady().catch((error) => {
1586
+ this.emailKeysReadyPromise = null;
1587
+ throw error;
1588
+ });
1589
+ return this.emailKeysReadyPromise;
1590
+ }
1591
+ async checkEmailKeysReady() {
1592
+ if (await new BackfillTracker({ db: this.requireDatabase() }).isApplied("@happyvertical/smrt-profiles:profile-email-keys:v1")) return;
1593
+ throw new CanonicalPersonProfileError("email_key_backfill_required", "Profile identity email keys are not marked ready; run backfillProfileEmailKeys() before identity lookup.");
1594
+ }
1595
+ async isStaleEmailReservation(reservation, reservedEmail) {
1596
+ const profileId = reservation.profileId;
1597
+ if (!profileId) return true;
1598
+ const row = (await this.requireDatabase().query("SELECT email FROM profiles WHERE id = ? LIMIT 1", profileId)).rows[0];
1599
+ if (!row) return true;
1600
+ const email = readString(row, "email");
1601
+ return !email?.trim() || normalizeIdentityEmail(email) !== reservedEmail;
1602
+ }
1603
+ assertRowEmailKeyCurrent(row, normalizedEmail) {
1604
+ const email = readString(row, "email") ?? "";
1605
+ const expectedKey = email.trim() ? normalizeIdentityEmail(email) : null;
1606
+ if ((readString(row, "email_key") ?? null) !== expectedKey || normalizedEmail !== void 0 && expectedKey !== normalizedEmail) throw new CanonicalPersonProfileError("email_key_backfill_required", `Profile ${String(row.id ?? "<unknown>")} has a missing or stale identity email key; run backfillProfileEmailKeys() before identity lookup.`);
1607
+ }
1608
+ assertCanonicalRows(rows, normalizedEmail) {
1609
+ const tenantScoped = rows.find((row) => readString(row, "tenant_id"));
1610
+ if (tenantScoped) throw new CanonicalPersonProfileError("tenant_scoped", `Profile ${readRequiredString(tenantScoped, "id")} for ${normalizedEmail ?? "the supplied identity"} is tenant-scoped.`);
1611
+ const nonPerson = rows.find((row) => !PERSON_META_TYPES.has(readString(row, "_meta_type") ?? ""));
1612
+ if (nonPerson) throw new CanonicalPersonProfileError("non_person", `Profile ${readRequiredString(nonPerson, "id")} for ${normalizedEmail ?? "the supplied identity"} is not a Person.`);
1613
+ if (rows.length > 1) throw new CanonicalPersonProfileError("ambiguous_email", `Multiple Profiles use ${normalizedEmail ?? "the supplied identity email"}.`);
1614
+ }
1615
+ async requireHydratedProfile(profileId) {
1616
+ const profile = await withSystemContext(() => this.get({ id: profileId }));
1617
+ if (!profile) throw new CanonicalPersonProfileError("missing_profile", `Profile ${profileId} could not be hydrated.`);
1618
+ profile.id = profileId;
1619
+ return profile;
1620
+ }
1621
+ /**
1622
+ * Batch get metadata for multiple profiles
1623
+ *
1624
+ * @param profileIds - Array of profile UUIDs
1625
+ * @returns Map of profile ID to metadata object
1626
+ */
1627
+ async batchGetMetadata(profileIds) {
1628
+ const result = /* @__PURE__ */ new Map();
1629
+ for (const profileId of profileIds) {
1630
+ const profile = await this.get({ id: profileId });
1631
+ if (profile) {
1632
+ const metadata = await profile.getMetadata();
1633
+ result.set(profileId, metadata);
1634
+ }
1635
+ }
1636
+ return result;
1637
+ }
1638
+ /**
1639
+ * Batch update metadata for multiple profiles
1640
+ *
1641
+ * @param updates - Array of { profileId, data } objects
1642
+ */
1643
+ async batchUpdateMetadata(updates) {
1644
+ for (const update of updates) {
1645
+ const profile = await this.get({ id: update.profileId });
1646
+ if (profile) await profile.updateMetadata(update.data);
1647
+ }
1648
+ }
1649
+ /**
1650
+ * Find related profiles for a given profile
1651
+ *
1652
+ * @param profileId - The profile UUID
1653
+ * @param relationshipSlug - Optional filter by relationship type
1654
+ * @returns Array of related profiles
1655
+ */
1656
+ async findRelated(profileId, relationshipSlug) {
1657
+ const profile = await this.get({ id: profileId });
1658
+ if (!profile) return [];
1659
+ return await profile.getRelatedProfiles(relationshipSlug);
1660
+ }
1661
+ async getAssets(profileId, relationship) {
1662
+ return getOwnedAssetsFromCollection(this, profileId, relationship);
1663
+ }
1664
+ async addAsset(profileId, asset, relationship = "attachment", sortOrder = 0) {
1665
+ await addOwnedAssetFromCollection(this, "Profile", profileId, asset, relationship, sortOrder);
1666
+ }
1667
+ async removeAsset(profileId, assetId, relationship) {
1668
+ await removeOwnedAssetFromCollection(this, "Profile", profileId, assetId, relationship);
1669
+ }
1670
+ /**
1671
+ * Get the relationship network for a profile up to a maximum depth
1672
+ *
1673
+ * @param profileId - The starting profile UUID
1674
+ * @param options - Configuration options
1675
+ * @returns Map of profile ID to depth level
1676
+ */
1677
+ async getRelationshipNetwork(profileId, options = {}) {
1678
+ const maxDepth = options.maxDepth || 2;
1679
+ const network = /* @__PURE__ */ new Map();
1680
+ const visited = /* @__PURE__ */ new Set();
1681
+ const queue = [{
1682
+ id: profileId,
1683
+ depth: 0
1684
+ }];
1685
+ while (queue.length > 0) {
1686
+ const current = queue.shift();
1687
+ if (!current) break;
1688
+ if (visited.has(current.id) || current.depth > maxDepth) continue;
1689
+ visited.add(current.id);
1690
+ network.set(current.id, current.depth);
1691
+ if (current.depth < maxDepth) {
1692
+ const related = await this.findRelated(current.id);
1693
+ for (const profile of related) if (profile.id && !visited.has(profile.id)) queue.push({
1694
+ id: profile.id,
1695
+ depth: current.depth + 1
1696
+ });
1697
+ }
1698
+ }
1699
+ return network;
1700
+ }
1701
+ /**
1702
+ * Find all profiles belonging to a specific tenant
1703
+ *
1704
+ * @param tenantId - The tenant UUID to filter by
1705
+ * @returns Array of profiles for this tenant
1706
+ */
1707
+ async findByTenant(tenantId) {
1708
+ return this.list({ where: { tenantId } });
1709
+ }
1710
+ /**
1711
+ * Find all global profiles (no tenant association).
1712
+ *
1713
+ * Routes through the shared tenant-global helper so it does not throw under
1714
+ * an active tenant context (an explicit `tenant_id IS NULL` filter would be
1715
+ * flagged as an isolation violation). (#1600)
1716
+ *
1717
+ * @returns Array of profiles with null tenantId
1718
+ */
1719
+ async findGlobal() {
1720
+ return queryGlobal(this);
1721
+ }
1722
+ /**
1723
+ * Find profiles belonging to a tenant plus all global profiles.
1724
+ *
1725
+ * Fails closed if an active tenant context requests a different tenant's
1726
+ * rows; the admin/system path keeps the cross-tenant capability. (#1600)
1727
+ *
1728
+ * @param tenantId - The tenant UUID to include
1729
+ * @returns Array of tenant-specific and global profiles
1730
+ */
1731
+ async findWithGlobals(tenantId) {
1732
+ return queryWithGlobals(this, tenantId, "Profile.findWithGlobals");
1733
+ }
1734
+ };
1735
+ async function saveIdentityEmailReservation(reservation, normalizedEmail) {
1736
+ try {
1737
+ await reservation.save();
1738
+ } catch (error) {
1739
+ if (error instanceof ValidationError && error.code === "VALIDATION_UNIQUE_CONSTRAINT" || isOidcAbortedTransactionError(error)) throw new CanonicalPersonProfileError("reservation_conflict", `${normalizedEmail} was concurrently reserved for another canonical Person.`);
1740
+ throw error;
1741
+ }
1742
+ }
1743
+ function readString(row, key) {
1744
+ const value = row[key];
1745
+ return typeof value === "string" && value.length > 0 ? value : null;
1746
+ }
1747
+ function readRequiredString(row, key) {
1748
+ const value = readString(row, key);
1749
+ if (!value) throw new Error(`Canonical Profile identity row is missing ${key}.`);
1750
+ return value;
1751
+ }
1752
+ //#endregion
1753
+ export { coordinateOidcProvisioning as a, saveOidcRaceArbiter as c, ProfileType as i, ProfileCollection as n, isOidcProvisioningRaceConflict as o, ProfileCollection_BvhD2cfL_exports as r, normalizeIdentityEmail as s, Profile as t };