@mastra/factory 0.2.2-alpha.0 → 0.2.2-alpha.2

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 (174) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/auth.d.ts +2 -2
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +401 -314
  5. package/dist/auth.js.map +1 -1
  6. package/dist/capabilities/connection.js +1 -1
  7. package/dist/capabilities/intake.js +1 -1
  8. package/dist/capabilities/version-control.js +1 -1
  9. package/dist/factory.d.ts.map +1 -1
  10. package/dist/factory.js +441 -39455
  11. package/dist/factory.js.map +1 -1
  12. package/dist/index.js +2 -39466
  13. package/dist/integrations/base.js +1 -1
  14. package/dist/integrations/github/config.js +36 -22
  15. package/dist/integrations/github/config.js.map +1 -1
  16. package/dist/integrations/github/factory-session.js +33 -37
  17. package/dist/integrations/github/factory-session.js.map +1 -1
  18. package/dist/integrations/github/integration.js +1037 -3624
  19. package/dist/integrations/github/integration.js.map +1 -1
  20. package/dist/integrations/github/issue-triage.js +71 -71
  21. package/dist/integrations/github/issue-triage.js.map +1 -1
  22. package/dist/integrations/github/pat.js +47 -36
  23. package/dist/integrations/github/pat.js.map +1 -1
  24. package/dist/integrations/github/provenance.js +72 -101
  25. package/dist/integrations/github/provenance.js.map +1 -1
  26. package/dist/integrations/github/routes.d.ts.map +1 -1
  27. package/dist/integrations/github/routes.js +1272 -1833
  28. package/dist/integrations/github/routes.js.map +1 -1
  29. package/dist/integrations/github/rules.js +203 -471
  30. package/dist/integrations/github/rules.js.map +1 -1
  31. package/dist/integrations/github/sandbox.d.ts +2 -0
  32. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  33. package/dist/integrations/github/sandbox.js +401 -326
  34. package/dist/integrations/github/sandbox.js.map +1 -1
  35. package/dist/integrations/github/session-subscriptions.js +165 -223
  36. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  37. package/dist/integrations/github/subscriptions.js +43 -51
  38. package/dist/integrations/github/subscriptions.js.map +1 -1
  39. package/dist/integrations/github/token-refresh.js +15 -18
  40. package/dist/integrations/github/token-refresh.js.map +1 -1
  41. package/dist/integrations/github/webhook.js +359 -343
  42. package/dist/integrations/github/webhook.js.map +1 -1
  43. package/dist/integrations/linear/agent-tools.js +77 -100
  44. package/dist/integrations/linear/agent-tools.js.map +1 -1
  45. package/dist/integrations/linear/integration.js +611 -1309
  46. package/dist/integrations/linear/integration.js.map +1 -1
  47. package/dist/integrations/linear/routes.js +227 -230
  48. package/dist/integrations/linear/routes.js.map +1 -1
  49. package/dist/integrations/linear/rules.js +126 -346
  50. package/dist/integrations/linear/rules.js.map +1 -1
  51. package/dist/integrations/linear/storage.js +1 -1
  52. package/dist/integrations/platform/api-client.js +131 -135
  53. package/dist/integrations/platform/api-client.js.map +1 -1
  54. package/dist/integrations/platform/github/event-worker.js +275 -573
  55. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  56. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  57. package/dist/integrations/platform/github/integration.js +845 -3942
  58. package/dist/integrations/platform/github/integration.js.map +1 -1
  59. package/dist/integrations/platform/linear/integration.js +457 -1309
  60. package/dist/integrations/platform/linear/integration.js.map +1 -1
  61. package/dist/integrations/workos/integration.js +80 -85
  62. package/dist/integrations/workos/integration.js.map +1 -1
  63. package/dist/routes/config.d.ts.map +1 -1
  64. package/dist/routes/config.js +933 -915
  65. package/dist/routes/config.js.map +1 -1
  66. package/dist/routes/custom-provider-source.js +110 -93
  67. package/dist/routes/custom-provider-source.js.map +1 -1
  68. package/dist/routes/fs.js +401 -349
  69. package/dist/routes/fs.js.map +1 -1
  70. package/dist/routes/intake.js +165 -165
  71. package/dist/routes/intake.js.map +1 -1
  72. package/dist/routes/oauth.js +447 -25166
  73. package/dist/routes/oauth.js.map +1 -1
  74. package/dist/routes/projects.js +437 -376
  75. package/dist/routes/projects.js.map +1 -1
  76. package/dist/routes/provider-credentials.js +76 -58
  77. package/dist/routes/provider-credentials.js.map +1 -1
  78. package/dist/routes/route.js +12 -8
  79. package/dist/routes/route.js.map +1 -1
  80. package/dist/routes/skills.js +160 -173
  81. package/dist/routes/skills.js.map +1 -1
  82. package/dist/routes/surface.js +250 -28618
  83. package/dist/routes/surface.js.map +1 -1
  84. package/dist/routes/tenant-credentials.js +141 -166
  85. package/dist/routes/tenant-credentials.js.map +1 -1
  86. package/dist/routes/work-items.js +654 -862
  87. package/dist/routes/work-items.js.map +1 -1
  88. package/dist/rules/binding-context.js +23 -35
  89. package/dist/rules/binding-context.js.map +1 -1
  90. package/dist/rules/defaults.js +200 -309
  91. package/dist/rules/defaults.js.map +1 -1
  92. package/dist/rules/dispatcher.d.ts.map +1 -1
  93. package/dist/rules/dispatcher.js +438 -677
  94. package/dist/rules/dispatcher.js.map +1 -1
  95. package/dist/rules/index.js +5 -599
  96. package/dist/rules/processor.js +344 -596
  97. package/dist/rules/processor.js.map +1 -1
  98. package/dist/rules/resolve.js +21 -18
  99. package/dist/rules/resolve.js.map +1 -1
  100. package/dist/rules/start-coordinator.js +168 -157
  101. package/dist/rules/start-coordinator.js.map +1 -1
  102. package/dist/rules/tools.js +50 -85
  103. package/dist/rules/tools.js.map +1 -1
  104. package/dist/rules/transition-service.js +183 -454
  105. package/dist/rules/transition-service.js.map +1 -1
  106. package/dist/rules/types.js +33 -29
  107. package/dist/rules/types.js.map +1 -1
  108. package/dist/rules/validation.js +266 -281
  109. package/dist/rules/validation.js.map +1 -1
  110. package/dist/sandbox/fleet.js +272 -250
  111. package/dist/sandbox/fleet.js.map +1 -1
  112. package/dist/sandbox/reattach.js +13 -6
  113. package/dist/sandbox/reattach.js.map +1 -1
  114. package/dist/server-error.js +12 -16
  115. package/dist/server-error.js.map +1 -1
  116. package/dist/skills/service.js +30 -35
  117. package/dist/skills/service.js.map +1 -1
  118. package/dist/spa-static.d.ts.map +1 -1
  119. package/dist/spa-static.js +91 -64
  120. package/dist/spa-static.js.map +1 -1
  121. package/dist/state-signing.js +73 -44
  122. package/dist/state-signing.js.map +1 -1
  123. package/dist/storage/domains/audit/agent-audit.js +60 -59
  124. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  125. package/dist/storage/domains/audit/base.js +169 -112
  126. package/dist/storage/domains/audit/base.js.map +1 -1
  127. package/dist/storage/domains/audit/domain.js +155 -153
  128. package/dist/storage/domains/audit/domain.js.map +1 -1
  129. package/dist/storage/domains/credentials/base.js +259 -243
  130. package/dist/storage/domains/credentials/base.js.map +1 -1
  131. package/dist/storage/domains/custom-providers/base.js +138 -143
  132. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  133. package/dist/storage/domains/intake/base.js +75 -57
  134. package/dist/storage/domains/intake/base.js.map +1 -1
  135. package/dist/storage/domains/integrations/base.js +310 -245
  136. package/dist/storage/domains/integrations/base.js.map +1 -1
  137. package/dist/storage/domains/memory-settings/base.js +117 -102
  138. package/dist/storage/domains/memory-settings/base.js.map +1 -1
  139. package/dist/storage/domains/model-packs/base.js +89 -88
  140. package/dist/storage/domains/model-packs/base.js.map +1 -1
  141. package/dist/storage/domains/projects/base.js +97 -92
  142. package/dist/storage/domains/projects/base.js.map +1 -1
  143. package/dist/storage/domains/queue-health/base.js +111 -76
  144. package/dist/storage/domains/queue-health/base.js.map +1 -1
  145. package/dist/storage/domains/source-control/base.d.ts +9 -0
  146. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  147. package/dist/storage/domains/source-control/base.js +777 -702
  148. package/dist/storage/domains/source-control/base.js.map +1 -1
  149. package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
  150. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  151. package/dist/storage/domains/source-control/inmemory.js +322 -344
  152. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  153. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  154. package/dist/storage/domains/work-items/base.js +1266 -1132
  155. package/dist/storage/domains/work-items/base.js.map +1 -1
  156. package/dist/storage/domains/work-items/metrics.js +202 -184
  157. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  158. package/dist/timing.d.ts +15 -0
  159. package/dist/timing.d.ts.map +1 -0
  160. package/dist/timing.js +34 -0
  161. package/dist/timing.js.map +1 -0
  162. package/dist/workspace.d.ts +2 -3
  163. package/dist/workspace.d.ts.map +1 -1
  164. package/dist/workspace.js +236 -438
  165. package/dist/workspace.js.map +1 -1
  166. package/factory-skills/factory-review/SKILL.md +9 -2
  167. package/package.json +9 -9
  168. package/dist/capabilities/connection.js.map +0 -1
  169. package/dist/capabilities/intake.js.map +0 -1
  170. package/dist/capabilities/version-control.js.map +0 -1
  171. package/dist/index.js.map +0 -1
  172. package/dist/integrations/base.js.map +0 -1
  173. package/dist/integrations/linear/storage.js.map +0 -1
  174. package/dist/rules/index.js.map +0 -1
@@ -1,257 +1,273 @@
1
- // src/storage/domains/credentials/base.ts
2
1
  import { FactoryStorageDomain, UniqueViolationError } from "@mastra/core/storage";
2
+ //#region src/storage/domains/credentials/base.ts
3
+ /** Mirror of `AuthStorage.getApiKey()`'s expiry check. */
3
4
  function isOAuthCredentialExpired(credential, now = Date.now()) {
4
- return now >= credential.expires;
5
+ return now >= credential.expires;
5
6
  }
7
+ /** OAuth plan credentials are personal and must always have an owning user. */
6
8
  function assertCredentialScope(tenant, credential) {
7
- if (credential.type === "oauth" && !tenant.userId) {
8
- throw new Error("OAuth credentials must be user-scoped");
9
- }
9
+ if (credential.type === "oauth" && !tenant.userId) throw new Error("OAuth credentials must be user-scoped");
10
10
  }
11
- var MODEL_CREDENTIALS_SCHEMA = {
12
- name: "model_provider_credentials",
13
- columns: {
14
- id: { type: "uuid-pk" },
15
- org_id: { type: "text" },
16
- user_id: { type: "text", nullable: true },
17
- provider: { type: "text" },
18
- type: { type: "text" },
19
- data: { type: "json" },
20
- created_at: { type: "timestamp" },
21
- updated_at: { type: "timestamp" }
22
- },
23
- uniqueIndexes: [
24
- // Scope is encoded in `user_id` nullability (NULL = org-scoped shared
25
- // row), enforced by two partial unique indexes.
26
- {
27
- name: "model_provider_credentials_user_unique",
28
- columns: ["org_id", "user_id", "provider"],
29
- whereNotNull: "user_id"
30
- },
31
- { name: "model_provider_credentials_org_unique", columns: ["org_id", "provider"], whereNull: "user_id" }
32
- ]
11
+ const MODEL_CREDENTIALS_SCHEMA = {
12
+ name: "model_provider_credentials",
13
+ columns: {
14
+ id: { type: "uuid-pk" },
15
+ org_id: { type: "text" },
16
+ user_id: {
17
+ type: "text",
18
+ nullable: true
19
+ },
20
+ provider: { type: "text" },
21
+ type: { type: "text" },
22
+ data: { type: "json" },
23
+ created_at: { type: "timestamp" },
24
+ updated_at: { type: "timestamp" }
25
+ },
26
+ uniqueIndexes: [{
27
+ name: "model_provider_credentials_user_unique",
28
+ columns: [
29
+ "org_id",
30
+ "user_id",
31
+ "provider"
32
+ ],
33
+ whereNotNull: "user_id"
34
+ }, {
35
+ name: "model_provider_credentials_org_unique",
36
+ columns: ["org_id", "provider"],
37
+ whereNull: "user_id"
38
+ }]
33
39
  };
34
- var OAUTH_LOGIN_SESSIONS_SCHEMA = {
35
- name: "oauth_login_sessions",
36
- columns: {
37
- session_id: { type: "text", primaryKey: true },
38
- org_id: { type: "text" },
39
- user_id: { type: "text" },
40
- provider: { type: "text" },
41
- kind: { type: "text" },
42
- pending: { type: "json" },
43
- expires_at: { type: "timestamp" },
44
- next_poll_at: { type: "timestamp", nullable: true },
45
- created_at: { type: "timestamp" }
46
- }
40
+ const OAUTH_LOGIN_SESSIONS_SCHEMA = {
41
+ name: "oauth_login_sessions",
42
+ columns: {
43
+ session_id: {
44
+ type: "text",
45
+ primaryKey: true
46
+ },
47
+ org_id: { type: "text" },
48
+ user_id: { type: "text" },
49
+ provider: { type: "text" },
50
+ kind: { type: "text" },
51
+ pending: { type: "json" },
52
+ expires_at: { type: "timestamp" },
53
+ next_poll_at: {
54
+ type: "timestamp",
55
+ nullable: true
56
+ },
57
+ created_at: { type: "timestamp" }
58
+ }
47
59
  };
48
60
  function toSessionRow(db) {
49
- return {
50
- sessionId: db.session_id,
51
- orgId: db.org_id,
52
- userId: db.user_id,
53
- provider: db.provider,
54
- kind: db.kind,
55
- pending: db.pending,
56
- expiresAt: db.expires_at,
57
- nextPollAt: db.next_poll_at,
58
- createdAt: db.created_at
59
- };
61
+ return {
62
+ sessionId: db.session_id,
63
+ orgId: db.org_id,
64
+ userId: db.user_id,
65
+ provider: db.provider,
66
+ kind: db.kind,
67
+ pending: db.pending,
68
+ expiresAt: db.expires_at,
69
+ nextPollAt: db.next_poll_at,
70
+ createdAt: db.created_at
71
+ };
60
72
  }
73
+ /** Filter selecting exactly the tenant's row for a provider (`null` = org row). */
61
74
  function tenantWhere(tenant, provider) {
62
- return { org_id: tenant.orgId, provider, user_id: tenant.userId ?? null };
75
+ return {
76
+ org_id: tenant.orgId,
77
+ provider,
78
+ user_id: tenant.userId ?? null
79
+ };
63
80
  }
81
+ /**
82
+ * Model-credentials storage, written once against the generic
83
+ * `FactoryStorageOps` surface. `refreshOAuth()` and `claimLoginSession()`
84
+ * ride `updateAtomic` so concurrent replicas serialize instead of
85
+ * invalidating each other's rotating tokens / double-claiming a flow.
86
+ */
64
87
  var ModelCredentialsStorage = class extends FactoryStorageDomain {
65
- constructor() {
66
- super("model-credentials");
67
- }
68
- async init() {
69
- await this.ensureCollections([MODEL_CREDENTIALS_SCHEMA, OAUTH_LOGIN_SESSIONS_SCHEMA]);
70
- }
71
- async dangerouslyClearAll() {
72
- await this.ops.deleteMany("oauth_login_sessions", {});
73
- await this.ops.deleteMany("model_provider_credentials", {});
74
- }
75
- get #db() {
76
- return this.ops;
77
- }
78
- /** Read the tenant's credential for a provider at exactly that scope. */
79
- async getCredential(tenant, provider) {
80
- const row = await this.#db.findOne("model_provider_credentials", tenantWhere(tenant, provider));
81
- return row?.data;
82
- }
83
- /** Upsert the tenant's credential (`created_at` is preserved on update). */
84
- async setCredential(tenant, provider, credential) {
85
- assertCredentialScope(tenant, credential);
86
- const where = tenantWhere(tenant, provider);
87
- const update = async () => this.#db.updateMany("model_provider_credentials", where, {
88
- type: credential.type,
89
- data: credential,
90
- updated_at: /* @__PURE__ */ new Date()
91
- });
92
- let lastError;
93
- for (let attempt = 0; attempt < 2; attempt++) {
94
- if (await update() > 0) return;
95
- const now = /* @__PURE__ */ new Date();
96
- try {
97
- await this.#db.insertOne("model_provider_credentials", {
98
- org_id: tenant.orgId,
99
- user_id: tenant.userId ?? null,
100
- provider,
101
- type: credential.type,
102
- data: credential,
103
- created_at: now,
104
- updated_at: now
105
- });
106
- return;
107
- } catch (error) {
108
- if (!(error instanceof UniqueViolationError)) throw error;
109
- lastError = error;
110
- }
111
- }
112
- throw lastError;
113
- }
114
- /** Delete the tenant's credential at exactly that scope. True when a row was removed. */
115
- async removeCredential(tenant, provider) {
116
- const deleted = await this.#db.deleteMany("model_provider_credentials", tenantWhere(tenant, provider));
117
- return deleted > 0;
118
- }
119
- /** All credentials visible to a user: their own rows plus the org's shared rows. */
120
- async listCredentials(orgId, userId) {
121
- const [userRows, orgRows] = await Promise.all([
122
- this.#db.findMany("model_provider_credentials", { org_id: orgId, user_id: userId }),
123
- this.#db.findMany("model_provider_credentials", { org_id: orgId, user_id: null })
124
- ]);
125
- return [
126
- ...userRows.map((row) => ({
127
- provider: row.provider,
128
- scope: "user",
129
- credential: row.data,
130
- updatedAt: row.updated_at
131
- })),
132
- ...orgRows.map((row) => ({
133
- provider: row.provider,
134
- scope: "org",
135
- credential: row.data,
136
- updatedAt: row.updated_at
137
- }))
138
- ];
139
- }
140
- /**
141
- * Resolve the credential a caller should use for a provider: the user's own
142
- * row wins over the org's shared row.
143
- */
144
- async resolveCredential(orgId, userId, provider) {
145
- const userRow = await this.#db.findOne("model_provider_credentials", {
146
- org_id: orgId,
147
- provider,
148
- user_id: userId
149
- });
150
- if (userRow) return { provider: userRow.provider, scope: "user", credential: userRow.data };
151
- const orgRow = await this.#db.findOne("model_provider_credentials", {
152
- org_id: orgId,
153
- provider,
154
- user_id: null
155
- });
156
- if (orgRow) return { provider: orgRow.provider, scope: "org", credential: orgRow.data };
157
- return void 0;
158
- }
159
- /**
160
- * Refresh the tenant's OAuth credential under the backend's atomic
161
- * read-modify-write so concurrent replicas don't invalidate each other's
162
- * rotating refresh tokens. The expiry is re-checked under the lock —
163
- * another replica may have refreshed already, in which case `refreshFn` is
164
- * skipped and the fresh credential is returned. Returns `undefined` when no
165
- * OAuth row exists for the tenant.
166
- */
167
- async refreshOAuth(tenant, provider, refreshFn) {
168
- let result;
169
- await this.#db.updateAtomic(
170
- "model_provider_credentials",
171
- tenantWhere(tenant, provider),
172
- async (row) => {
173
- if (row.data.type !== "oauth") return null;
174
- const current = row.data;
175
- if (!isOAuthCredentialExpired(current)) {
176
- result = current;
177
- return null;
178
- }
179
- const next = await refreshFn(current);
180
- result = next;
181
- return { data: next, updated_at: /* @__PURE__ */ new Date() };
182
- }
183
- );
184
- return result;
185
- }
186
- /** Persist a pending login flow started by a web route. */
187
- async createLoginSession(input) {
188
- const inserted = await this.#db.insertOne("oauth_login_sessions", {
189
- session_id: input.sessionId,
190
- org_id: input.orgId,
191
- user_id: input.userId,
192
- provider: input.provider,
193
- kind: input.kind,
194
- pending: input.pending,
195
- expires_at: input.expiresAt,
196
- next_poll_at: input.nextPollAt ?? null,
197
- created_at: /* @__PURE__ */ new Date()
198
- });
199
- return toSessionRow(inserted);
200
- }
201
- /**
202
- * Read a pending login flow. An expired session is deleted on read (TTL
203
- * cleanup) and reported as absent.
204
- */
205
- async getLoginSession(sessionId) {
206
- const row = await this.#db.findOne("oauth_login_sessions", { session_id: sessionId });
207
- if (!row) return void 0;
208
- if (row.expires_at.getTime() <= Date.now()) {
209
- await this.#db.deleteMany("oauth_login_sessions", { session_id: sessionId });
210
- return void 0;
211
- }
212
- return toSessionRow(row);
213
- }
214
- /**
215
- * Atomically claim a due login session for one upstream completion/poll attempt.
216
- * Returns undefined when the session is missing, expired, owned by another
217
- * tenant/provider, or already claimed by a concurrent request.
218
- */
219
- async claimLoginSession(sessionId, owner) {
220
- const now = Date.now();
221
- let claimed = false;
222
- const updated = await this.#db.updateAtomic(
223
- "oauth_login_sessions",
224
- { session_id: sessionId },
225
- (row) => {
226
- if (row.org_id !== owner.orgId || row.user_id !== owner.userId || row.provider !== owner.provider || row.kind !== owner.kind) {
227
- return null;
228
- }
229
- if (row.expires_at.getTime() <= now) return null;
230
- if (row.next_poll_at && row.next_poll_at.getTime() > now) return null;
231
- claimed = true;
232
- return { next_poll_at: row.expires_at };
233
- }
234
- );
235
- return claimed && updated ? toSessionRow(updated) : void 0;
236
- }
237
- /** Update a pending flow's serialized state and/or next allowed poll time. */
238
- async touchLoginSession(sessionId, updates) {
239
- const set = {};
240
- if (updates.pending !== void 0) set.pending = updates.pending;
241
- if (updates.nextPollAt !== void 0) set.next_poll_at = updates.nextPollAt;
242
- if (Object.keys(set).length === 0) return;
243
- await this.#db.updateMany("oauth_login_sessions", { session_id: sessionId }, set);
244
- }
245
- /** Remove a pending login flow (completed, cancelled, or failed). */
246
- async deleteLoginSession(sessionId) {
247
- await this.#db.deleteMany("oauth_login_sessions", { session_id: sessionId });
248
- }
249
- };
250
- export {
251
- MODEL_CREDENTIALS_SCHEMA,
252
- ModelCredentialsStorage,
253
- OAUTH_LOGIN_SESSIONS_SCHEMA,
254
- assertCredentialScope,
255
- isOAuthCredentialExpired
88
+ constructor() {
89
+ super("model-credentials");
90
+ }
91
+ async init() {
92
+ await this.ensureCollections([MODEL_CREDENTIALS_SCHEMA, OAUTH_LOGIN_SESSIONS_SCHEMA]);
93
+ }
94
+ async dangerouslyClearAll() {
95
+ await this.ops.deleteMany("oauth_login_sessions", {});
96
+ await this.ops.deleteMany("model_provider_credentials", {});
97
+ }
98
+ get #db() {
99
+ return this.ops;
100
+ }
101
+ /** Read the tenant's credential for a provider at exactly that scope. */
102
+ async getCredential(tenant, provider) {
103
+ return (await this.#db.findOne("model_provider_credentials", tenantWhere(tenant, provider)))?.data;
104
+ }
105
+ /** Upsert the tenant's credential (`created_at` is preserved on update). */
106
+ async setCredential(tenant, provider, credential) {
107
+ assertCredentialScope(tenant, credential);
108
+ const where = tenantWhere(tenant, provider);
109
+ const update = async () => this.#db.updateMany("model_provider_credentials", where, {
110
+ type: credential.type,
111
+ data: credential,
112
+ updated_at: /* @__PURE__ */ new Date()
113
+ });
114
+ let lastError;
115
+ for (let attempt = 0; attempt < 2; attempt++) {
116
+ if (await update() > 0) return;
117
+ const now = /* @__PURE__ */ new Date();
118
+ try {
119
+ await this.#db.insertOne("model_provider_credentials", {
120
+ org_id: tenant.orgId,
121
+ user_id: tenant.userId ?? null,
122
+ provider,
123
+ type: credential.type,
124
+ data: credential,
125
+ created_at: now,
126
+ updated_at: now
127
+ });
128
+ return;
129
+ } catch (error) {
130
+ if (!(error instanceof UniqueViolationError)) throw error;
131
+ lastError = error;
132
+ }
133
+ }
134
+ throw lastError;
135
+ }
136
+ /** Delete the tenant's credential at exactly that scope. True when a row was removed. */
137
+ async removeCredential(tenant, provider) {
138
+ return await this.#db.deleteMany("model_provider_credentials", tenantWhere(tenant, provider)) > 0;
139
+ }
140
+ /** All credentials visible to a user: their own rows plus the org's shared rows. */
141
+ async listCredentials(orgId, userId) {
142
+ const [userRows, orgRows] = await Promise.all([this.#db.findMany("model_provider_credentials", {
143
+ org_id: orgId,
144
+ user_id: userId
145
+ }), this.#db.findMany("model_provider_credentials", {
146
+ org_id: orgId,
147
+ user_id: null
148
+ })]);
149
+ return [...userRows.map((row) => ({
150
+ provider: row.provider,
151
+ scope: "user",
152
+ credential: row.data,
153
+ updatedAt: row.updated_at
154
+ })), ...orgRows.map((row) => ({
155
+ provider: row.provider,
156
+ scope: "org",
157
+ credential: row.data,
158
+ updatedAt: row.updated_at
159
+ }))];
160
+ }
161
+ /**
162
+ * Resolve the credential a caller should use for a provider: the user's own
163
+ * row wins over the org's shared row.
164
+ */
165
+ async resolveCredential(orgId, userId, provider) {
166
+ const userRow = await this.#db.findOne("model_provider_credentials", {
167
+ org_id: orgId,
168
+ provider,
169
+ user_id: userId
170
+ });
171
+ if (userRow) return {
172
+ provider: userRow.provider,
173
+ scope: "user",
174
+ credential: userRow.data
175
+ };
176
+ const orgRow = await this.#db.findOne("model_provider_credentials", {
177
+ org_id: orgId,
178
+ provider,
179
+ user_id: null
180
+ });
181
+ if (orgRow) return {
182
+ provider: orgRow.provider,
183
+ scope: "org",
184
+ credential: orgRow.data
185
+ };
186
+ }
187
+ /**
188
+ * Refresh the tenant's OAuth credential under the backend's atomic
189
+ * read-modify-write so concurrent replicas don't invalidate each other's
190
+ * rotating refresh tokens. The expiry is re-checked under the lock —
191
+ * another replica may have refreshed already, in which case `refreshFn` is
192
+ * skipped and the fresh credential is returned. Returns `undefined` when no
193
+ * OAuth row exists for the tenant.
194
+ */
195
+ async refreshOAuth(tenant, provider, refreshFn) {
196
+ let result;
197
+ await this.#db.updateAtomic("model_provider_credentials", tenantWhere(tenant, provider), async (row) => {
198
+ if (row.data.type !== "oauth") return null;
199
+ const current = row.data;
200
+ if (!isOAuthCredentialExpired(current)) {
201
+ result = current;
202
+ return null;
203
+ }
204
+ const next = await refreshFn(current);
205
+ result = next;
206
+ return {
207
+ data: next,
208
+ updated_at: /* @__PURE__ */ new Date()
209
+ };
210
+ });
211
+ return result;
212
+ }
213
+ /** Persist a pending login flow started by a web route. */
214
+ async createLoginSession(input) {
215
+ return toSessionRow(await this.#db.insertOne("oauth_login_sessions", {
216
+ session_id: input.sessionId,
217
+ org_id: input.orgId,
218
+ user_id: input.userId,
219
+ provider: input.provider,
220
+ kind: input.kind,
221
+ pending: input.pending,
222
+ expires_at: input.expiresAt,
223
+ next_poll_at: input.nextPollAt ?? null,
224
+ created_at: /* @__PURE__ */ new Date()
225
+ }));
226
+ }
227
+ /**
228
+ * Read a pending login flow. An expired session is deleted on read (TTL
229
+ * cleanup) and reported as absent.
230
+ */
231
+ async getLoginSession(sessionId) {
232
+ const row = await this.#db.findOne("oauth_login_sessions", { session_id: sessionId });
233
+ if (!row) return void 0;
234
+ if (row.expires_at.getTime() <= Date.now()) {
235
+ await this.#db.deleteMany("oauth_login_sessions", { session_id: sessionId });
236
+ return;
237
+ }
238
+ return toSessionRow(row);
239
+ }
240
+ /**
241
+ * Atomically claim a due login session for one upstream completion/poll attempt.
242
+ * Returns undefined when the session is missing, expired, owned by another
243
+ * tenant/provider, or already claimed by a concurrent request.
244
+ */
245
+ async claimLoginSession(sessionId, owner) {
246
+ const now = Date.now();
247
+ let claimed = false;
248
+ const updated = await this.#db.updateAtomic("oauth_login_sessions", { session_id: sessionId }, (row) => {
249
+ if (row.org_id !== owner.orgId || row.user_id !== owner.userId || row.provider !== owner.provider || row.kind !== owner.kind) return null;
250
+ if (row.expires_at.getTime() <= now) return null;
251
+ if (row.next_poll_at && row.next_poll_at.getTime() > now) return null;
252
+ claimed = true;
253
+ return { next_poll_at: row.expires_at };
254
+ });
255
+ return claimed && updated ? toSessionRow(updated) : void 0;
256
+ }
257
+ /** Update a pending flow's serialized state and/or next allowed poll time. */
258
+ async touchLoginSession(sessionId, updates) {
259
+ const set = {};
260
+ if (updates.pending !== void 0) set.pending = updates.pending;
261
+ if (updates.nextPollAt !== void 0) set.next_poll_at = updates.nextPollAt;
262
+ if (Object.keys(set).length === 0) return;
263
+ await this.#db.updateMany("oauth_login_sessions", { session_id: sessionId }, set);
264
+ }
265
+ /** Remove a pending login flow (completed, cancelled, or failed). */
266
+ async deleteLoginSession(sessionId) {
267
+ await this.#db.deleteMany("oauth_login_sessions", { session_id: sessionId });
268
+ }
256
269
  };
270
+ //#endregion
271
+ export { MODEL_CREDENTIALS_SCHEMA, ModelCredentialsStorage, OAUTH_LOGIN_SESSIONS_SCHEMA, assertCredentialScope, isOAuthCredentialExpired };
272
+
257
273
  //# sourceMappingURL=base.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/storage/domains/credentials/base.ts"],"sourcesContent":["/**\n * Model provider credentials domain — tenant-scoped OAuth tokens and API keys\n * for model providers (Anthropic/Claude Max, OpenAI Codex, GitHub Copilot,\n * xAI, plus plain API-key providers).\n *\n * Tenancy is two-level:\n * - **user-scoped** rows (`userId` present): personal plan OAuth tokens and\n * personal API keys. OAuth plan tokens are ONLY ever user-scoped — they are\n * personal subscriptions, and sharing one would bill/rate-limit a single\n * account for the whole org.\n * - **org-scoped** rows (`userId` absent): shared API keys set by an org\n * admin, inherited by every member.\n *\n * Resolution order at model-call time is user > org (the caller layers server\n * env vars underneath as a final fallback).\n *\n * Secrets posture: credentials are stored server-side only and never returned\n * to the client (same as `linear_connections.access_token`). Encryption at\n * rest is out of scope, matching the existing posture.\n *\n * The domain also owns `oauth_login_sessions`: pending web login flows\n * (paste-code PKCE verifiers, device-code poll state) persisted server-side so\n * any replica can complete or poll a flow started on another.\n */\n\nimport type { AuthCredential, OAuthCredential } from '@mastra/code-sdk/auth/types';\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, CollectionWhere, FactoryStorageOps } from '@mastra/core/storage';\n\n/** Owning tenant of a credential row. `userId` absent = org-scoped row. */\nexport interface CredentialTenant {\n orgId: string;\n userId?: string;\n}\n\n/** Which tenancy level a credential row lives at. */\nexport type CredentialScope = 'user' | 'org';\n\n/** One stored credential, annotated with its scope. */\nexport interface CredentialRecord {\n provider: string;\n scope: CredentialScope;\n credential: AuthCredential;\n updatedAt: Date;\n}\n\n/** Result of per-caller resolution: the winning credential and its scope. */\nexport interface ResolvedCredential {\n provider: string;\n scope: CredentialScope;\n credential: AuthCredential;\n}\n\n/** Kind of pending web login flow a session row tracks. */\nexport type LoginSessionKind = 'paste-code' | 'device-code';\n\n/** One pending OAuth login flow, persisted so any replica can continue it. */\nexport interface LoginSessionRow {\n sessionId: string;\n orgId: string;\n userId: string;\n provider: string;\n kind: LoginSessionKind;\n /** Serialized flow state (PKCE verifier, device-code pending state, ...). */\n pending: Record<string, unknown>;\n expiresAt: Date;\n /** Earliest time the next upstream poll is allowed (device-code flows). */\n nextPollAt: Date | null;\n createdAt: Date;\n}\n\nexport interface CreateLoginSessionInput {\n sessionId: string;\n orgId: string;\n userId: string;\n provider: string;\n kind: LoginSessionKind;\n pending: Record<string, unknown>;\n expiresAt: Date;\n nextPollAt?: Date | null;\n}\n\n/** Mirror of `AuthStorage.getApiKey()`'s expiry check. */\nexport function isOAuthCredentialExpired(credential: OAuthCredential, now = Date.now()): boolean {\n return now >= credential.expires;\n}\n\n/** OAuth plan credentials are personal and must always have an owning user. */\nexport function assertCredentialScope(tenant: CredentialTenant, credential: AuthCredential): void {\n if (credential.type === 'oauth' && !tenant.userId) {\n throw new Error('OAuth credentials must be user-scoped');\n }\n}\n\nexport const MODEL_CREDENTIALS_SCHEMA: CollectionSchema = {\n name: 'model_provider_credentials',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n user_id: { type: 'text', nullable: true },\n provider: { type: 'text' },\n type: { type: 'text' },\n data: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [\n // Scope is encoded in `user_id` nullability (NULL = org-scoped shared\n // row), enforced by two partial unique indexes.\n {\n name: 'model_provider_credentials_user_unique',\n columns: ['org_id', 'user_id', 'provider'],\n whereNotNull: 'user_id',\n },\n { name: 'model_provider_credentials_org_unique', columns: ['org_id', 'provider'], whereNull: 'user_id' },\n ],\n};\n\nexport const OAUTH_LOGIN_SESSIONS_SCHEMA: CollectionSchema = {\n name: 'oauth_login_sessions',\n columns: {\n session_id: { type: 'text', primaryKey: true },\n org_id: { type: 'text' },\n user_id: { type: 'text' },\n provider: { type: 'text' },\n kind: { type: 'text' },\n pending: { type: 'json' },\n expires_at: { type: 'timestamp' },\n next_poll_at: { type: 'timestamp', nullable: true },\n created_at: { type: 'timestamp' },\n },\n};\n\n/** Column shape of one `model_provider_credentials` row as returned by ops. */\ninterface CredentialDbRow extends Record<string, unknown> {\n id: string;\n provider: string;\n user_id: string | null;\n data: AuthCredential;\n updated_at: Date;\n}\n\n/** Column shape of one `oauth_login_sessions` row as returned by ops. */\ninterface LoginSessionDbRow extends Record<string, unknown> {\n session_id: string;\n org_id: string;\n user_id: string;\n provider: string;\n kind: LoginSessionKind;\n pending: Record<string, unknown>;\n expires_at: Date;\n next_poll_at: Date | null;\n created_at: Date;\n}\n\nfunction toSessionRow(db: LoginSessionDbRow): LoginSessionRow {\n return {\n sessionId: db.session_id,\n orgId: db.org_id,\n userId: db.user_id,\n provider: db.provider,\n kind: db.kind,\n pending: db.pending,\n expiresAt: db.expires_at,\n nextPollAt: db.next_poll_at,\n createdAt: db.created_at,\n };\n}\n\n/** Filter selecting exactly the tenant's row for a provider (`null` = org row). */\nfunction tenantWhere(tenant: CredentialTenant, provider: string): CollectionWhere {\n return { org_id: tenant.orgId, provider, user_id: tenant.userId ?? null };\n}\n\n/**\n * Model-credentials storage, written once against the generic\n * `FactoryStorageOps` surface. `refreshOAuth()` and `claimLoginSession()`\n * ride `updateAtomic` so concurrent replicas serialize instead of\n * invalidating each other's rotating tokens / double-claiming a flow.\n */\nexport class ModelCredentialsStorage extends FactoryStorageDomain {\n constructor() {\n super('model-credentials');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([MODEL_CREDENTIALS_SCHEMA, OAUTH_LOGIN_SESSIONS_SCHEMA]);\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('oauth_login_sessions', {});\n await this.ops.deleteMany('model_provider_credentials', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n /** Read the tenant's credential for a provider at exactly that scope. */\n async getCredential(tenant: CredentialTenant, provider: string): Promise<AuthCredential | undefined> {\n const row = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', tenantWhere(tenant, provider));\n return row?.data;\n }\n\n /** Upsert the tenant's credential (`created_at` is preserved on update). */\n async setCredential(tenant: CredentialTenant, provider: string, credential: AuthCredential): Promise<void> {\n assertCredentialScope(tenant, credential);\n const where = tenantWhere(tenant, provider);\n const update = async () =>\n this.#db.updateMany('model_provider_credentials', where, {\n type: credential.type,\n data: credential,\n updated_at: new Date(),\n });\n\n let lastError: unknown;\n for (let attempt = 0; attempt < 2; attempt++) {\n if ((await update()) > 0) return;\n\n const now = new Date();\n try {\n await this.#db.insertOne<CredentialDbRow>('model_provider_credentials', {\n org_id: tenant.orgId,\n user_id: tenant.userId ?? null,\n provider,\n type: credential.type,\n data: credential,\n created_at: now,\n updated_at: now,\n });\n return;\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n lastError = error;\n }\n }\n throw lastError;\n }\n\n /** Delete the tenant's credential at exactly that scope. True when a row was removed. */\n async removeCredential(tenant: CredentialTenant, provider: string): Promise<boolean> {\n const deleted = await this.#db.deleteMany('model_provider_credentials', tenantWhere(tenant, provider));\n return deleted > 0;\n }\n\n /** All credentials visible to a user: their own rows plus the org's shared rows. */\n async listCredentials(orgId: string, userId: string): Promise<CredentialRecord[]> {\n // SQL `IN` never matches NULL, so the org rows need their own query.\n const [userRows, orgRows] = await Promise.all([\n this.#db.findMany<CredentialDbRow>('model_provider_credentials', { org_id: orgId, user_id: userId }),\n this.#db.findMany<CredentialDbRow>('model_provider_credentials', { org_id: orgId, user_id: null }),\n ]);\n return [\n ...userRows.map(row => ({\n provider: row.provider,\n scope: 'user' as const,\n credential: row.data,\n updatedAt: row.updated_at,\n })),\n ...orgRows.map(row => ({\n provider: row.provider,\n scope: 'org' as const,\n credential: row.data,\n updatedAt: row.updated_at,\n })),\n ];\n }\n\n /**\n * Resolve the credential a caller should use for a provider: the user's own\n * row wins over the org's shared row.\n */\n async resolveCredential(orgId: string, userId: string, provider: string): Promise<ResolvedCredential | undefined> {\n const userRow = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', {\n org_id: orgId,\n provider,\n user_id: userId,\n });\n if (userRow) return { provider: userRow.provider, scope: 'user', credential: userRow.data };\n const orgRow = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', {\n org_id: orgId,\n provider,\n user_id: null,\n });\n if (orgRow) return { provider: orgRow.provider, scope: 'org', credential: orgRow.data };\n return undefined;\n }\n\n /**\n * Refresh the tenant's OAuth credential under the backend's atomic\n * read-modify-write so concurrent replicas don't invalidate each other's\n * rotating refresh tokens. The expiry is re-checked under the lock —\n * another replica may have refreshed already, in which case `refreshFn` is\n * skipped and the fresh credential is returned. Returns `undefined` when no\n * OAuth row exists for the tenant.\n */\n async refreshOAuth(\n tenant: CredentialTenant,\n provider: string,\n refreshFn: (current: OAuthCredential) => Promise<OAuthCredential>,\n ): Promise<OAuthCredential | undefined> {\n let result: OAuthCredential | undefined;\n await this.#db.updateAtomic<CredentialDbRow>(\n 'model_provider_credentials',\n tenantWhere(tenant, provider),\n async row => {\n if (row.data.type !== 'oauth') return null;\n const current = row.data;\n // Re-check under the lock: another replica may have refreshed while we waited.\n if (!isOAuthCredentialExpired(current)) {\n result = current;\n return null;\n }\n const next = await refreshFn(current);\n result = next;\n return { data: next, updated_at: new Date() };\n },\n );\n return result;\n }\n\n /** Persist a pending login flow started by a web route. */\n async createLoginSession(input: CreateLoginSessionInput): Promise<LoginSessionRow> {\n const inserted = await this.#db.insertOne<LoginSessionDbRow>('oauth_login_sessions', {\n session_id: input.sessionId,\n org_id: input.orgId,\n user_id: input.userId,\n provider: input.provider,\n kind: input.kind,\n pending: input.pending,\n expires_at: input.expiresAt,\n next_poll_at: input.nextPollAt ?? null,\n created_at: new Date(),\n });\n return toSessionRow(inserted);\n }\n\n /**\n * Read a pending login flow. An expired session is deleted on read (TTL\n * cleanup) and reported as absent.\n */\n async getLoginSession(sessionId: string): Promise<LoginSessionRow | undefined> {\n const row = await this.#db.findOne<LoginSessionDbRow>('oauth_login_sessions', { session_id: sessionId });\n if (!row) return undefined;\n if (row.expires_at.getTime() <= Date.now()) {\n await this.#db.deleteMany('oauth_login_sessions', { session_id: sessionId });\n return undefined;\n }\n return toSessionRow(row);\n }\n\n /**\n * Atomically claim a due login session for one upstream completion/poll attempt.\n * Returns undefined when the session is missing, expired, owned by another\n * tenant/provider, or already claimed by a concurrent request.\n */\n async claimLoginSession(\n sessionId: string,\n owner: Pick<LoginSessionRow, 'orgId' | 'userId' | 'provider' | 'kind'>,\n ): Promise<LoginSessionRow | undefined> {\n const now = Date.now();\n let claimed = false;\n const updated = await this.#db.updateAtomic<LoginSessionDbRow>(\n 'oauth_login_sessions',\n { session_id: sessionId },\n row => {\n if (\n row.org_id !== owner.orgId ||\n row.user_id !== owner.userId ||\n row.provider !== owner.provider ||\n row.kind !== owner.kind\n ) {\n return null;\n }\n if (row.expires_at.getTime() <= now) return null;\n if (row.next_poll_at && row.next_poll_at.getTime() > now) return null;\n claimed = true;\n // Park the next poll at expiry so a concurrent claim loses until the\n // claimer either touches the session forward or deletes it.\n return { next_poll_at: row.expires_at };\n },\n );\n return claimed && updated ? toSessionRow(updated) : undefined;\n }\n\n /** Update a pending flow's serialized state and/or next allowed poll time. */\n async touchLoginSession(\n sessionId: string,\n updates: { pending?: Record<string, unknown>; nextPollAt?: Date | null },\n ): Promise<void> {\n const set: Record<string, unknown> = {};\n if (updates.pending !== undefined) set.pending = updates.pending;\n if (updates.nextPollAt !== undefined) set.next_poll_at = updates.nextPollAt;\n if (Object.keys(set).length === 0) return;\n await this.#db.updateMany('oauth_login_sessions', { session_id: sessionId }, set);\n }\n\n /** Remove a pending login flow (completed, cancelled, or failed). */\n async deleteLoginSession(sessionId: string): Promise<void> {\n await this.#db.deleteMany('oauth_login_sessions', { session_id: sessionId });\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"file":"base.js","names":["#db"],"sources":["../../../../src/storage/domains/credentials/base.ts"],"sourcesContent":["/**\n * Model provider credentials domain — tenant-scoped OAuth tokens and API keys\n * for model providers (Anthropic/Claude Max, OpenAI Codex, GitHub Copilot,\n * xAI, plus plain API-key providers).\n *\n * Tenancy is two-level:\n * - **user-scoped** rows (`userId` present): personal plan OAuth tokens and\n * personal API keys. OAuth plan tokens are ONLY ever user-scoped — they are\n * personal subscriptions, and sharing one would bill/rate-limit a single\n * account for the whole org.\n * - **org-scoped** rows (`userId` absent): shared API keys set by an org\n * admin, inherited by every member.\n *\n * Resolution order at model-call time is user > org (the caller layers server\n * env vars underneath as a final fallback).\n *\n * Secrets posture: credentials are stored server-side only and never returned\n * to the client (same as `linear_connections.access_token`). Encryption at\n * rest is out of scope, matching the existing posture.\n *\n * The domain also owns `oauth_login_sessions`: pending web login flows\n * (paste-code PKCE verifiers, device-code poll state) persisted server-side so\n * any replica can complete or poll a flow started on another.\n */\n\nimport type { AuthCredential, OAuthCredential } from '@mastra/code-sdk/auth/types';\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, CollectionWhere, FactoryStorageOps } from '@mastra/core/storage';\n\n/** Owning tenant of a credential row. `userId` absent = org-scoped row. */\nexport interface CredentialTenant {\n orgId: string;\n userId?: string;\n}\n\n/** Which tenancy level a credential row lives at. */\nexport type CredentialScope = 'user' | 'org';\n\n/** One stored credential, annotated with its scope. */\nexport interface CredentialRecord {\n provider: string;\n scope: CredentialScope;\n credential: AuthCredential;\n updatedAt: Date;\n}\n\n/** Result of per-caller resolution: the winning credential and its scope. */\nexport interface ResolvedCredential {\n provider: string;\n scope: CredentialScope;\n credential: AuthCredential;\n}\n\n/** Kind of pending web login flow a session row tracks. */\nexport type LoginSessionKind = 'paste-code' | 'device-code';\n\n/** One pending OAuth login flow, persisted so any replica can continue it. */\nexport interface LoginSessionRow {\n sessionId: string;\n orgId: string;\n userId: string;\n provider: string;\n kind: LoginSessionKind;\n /** Serialized flow state (PKCE verifier, device-code pending state, ...). */\n pending: Record<string, unknown>;\n expiresAt: Date;\n /** Earliest time the next upstream poll is allowed (device-code flows). */\n nextPollAt: Date | null;\n createdAt: Date;\n}\n\nexport interface CreateLoginSessionInput {\n sessionId: string;\n orgId: string;\n userId: string;\n provider: string;\n kind: LoginSessionKind;\n pending: Record<string, unknown>;\n expiresAt: Date;\n nextPollAt?: Date | null;\n}\n\n/** Mirror of `AuthStorage.getApiKey()`'s expiry check. */\nexport function isOAuthCredentialExpired(credential: OAuthCredential, now = Date.now()): boolean {\n return now >= credential.expires;\n}\n\n/** OAuth plan credentials are personal and must always have an owning user. */\nexport function assertCredentialScope(tenant: CredentialTenant, credential: AuthCredential): void {\n if (credential.type === 'oauth' && !tenant.userId) {\n throw new Error('OAuth credentials must be user-scoped');\n }\n}\n\nexport const MODEL_CREDENTIALS_SCHEMA: CollectionSchema = {\n name: 'model_provider_credentials',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n user_id: { type: 'text', nullable: true },\n provider: { type: 'text' },\n type: { type: 'text' },\n data: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [\n // Scope is encoded in `user_id` nullability (NULL = org-scoped shared\n // row), enforced by two partial unique indexes.\n {\n name: 'model_provider_credentials_user_unique',\n columns: ['org_id', 'user_id', 'provider'],\n whereNotNull: 'user_id',\n },\n { name: 'model_provider_credentials_org_unique', columns: ['org_id', 'provider'], whereNull: 'user_id' },\n ],\n};\n\nexport const OAUTH_LOGIN_SESSIONS_SCHEMA: CollectionSchema = {\n name: 'oauth_login_sessions',\n columns: {\n session_id: { type: 'text', primaryKey: true },\n org_id: { type: 'text' },\n user_id: { type: 'text' },\n provider: { type: 'text' },\n kind: { type: 'text' },\n pending: { type: 'json' },\n expires_at: { type: 'timestamp' },\n next_poll_at: { type: 'timestamp', nullable: true },\n created_at: { type: 'timestamp' },\n },\n};\n\n/** Column shape of one `model_provider_credentials` row as returned by ops. */\ninterface CredentialDbRow extends Record<string, unknown> {\n id: string;\n provider: string;\n user_id: string | null;\n data: AuthCredential;\n updated_at: Date;\n}\n\n/** Column shape of one `oauth_login_sessions` row as returned by ops. */\ninterface LoginSessionDbRow extends Record<string, unknown> {\n session_id: string;\n org_id: string;\n user_id: string;\n provider: string;\n kind: LoginSessionKind;\n pending: Record<string, unknown>;\n expires_at: Date;\n next_poll_at: Date | null;\n created_at: Date;\n}\n\nfunction toSessionRow(db: LoginSessionDbRow): LoginSessionRow {\n return {\n sessionId: db.session_id,\n orgId: db.org_id,\n userId: db.user_id,\n provider: db.provider,\n kind: db.kind,\n pending: db.pending,\n expiresAt: db.expires_at,\n nextPollAt: db.next_poll_at,\n createdAt: db.created_at,\n };\n}\n\n/** Filter selecting exactly the tenant's row for a provider (`null` = org row). */\nfunction tenantWhere(tenant: CredentialTenant, provider: string): CollectionWhere {\n return { org_id: tenant.orgId, provider, user_id: tenant.userId ?? null };\n}\n\n/**\n * Model-credentials storage, written once against the generic\n * `FactoryStorageOps` surface. `refreshOAuth()` and `claimLoginSession()`\n * ride `updateAtomic` so concurrent replicas serialize instead of\n * invalidating each other's rotating tokens / double-claiming a flow.\n */\nexport class ModelCredentialsStorage extends FactoryStorageDomain {\n constructor() {\n super('model-credentials');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([MODEL_CREDENTIALS_SCHEMA, OAUTH_LOGIN_SESSIONS_SCHEMA]);\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('oauth_login_sessions', {});\n await this.ops.deleteMany('model_provider_credentials', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n /** Read the tenant's credential for a provider at exactly that scope. */\n async getCredential(tenant: CredentialTenant, provider: string): Promise<AuthCredential | undefined> {\n const row = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', tenantWhere(tenant, provider));\n return row?.data;\n }\n\n /** Upsert the tenant's credential (`created_at` is preserved on update). */\n async setCredential(tenant: CredentialTenant, provider: string, credential: AuthCredential): Promise<void> {\n assertCredentialScope(tenant, credential);\n const where = tenantWhere(tenant, provider);\n const update = async () =>\n this.#db.updateMany('model_provider_credentials', where, {\n type: credential.type,\n data: credential,\n updated_at: new Date(),\n });\n\n let lastError: unknown;\n for (let attempt = 0; attempt < 2; attempt++) {\n if ((await update()) > 0) return;\n\n const now = new Date();\n try {\n await this.#db.insertOne<CredentialDbRow>('model_provider_credentials', {\n org_id: tenant.orgId,\n user_id: tenant.userId ?? null,\n provider,\n type: credential.type,\n data: credential,\n created_at: now,\n updated_at: now,\n });\n return;\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n lastError = error;\n }\n }\n throw lastError;\n }\n\n /** Delete the tenant's credential at exactly that scope. True when a row was removed. */\n async removeCredential(tenant: CredentialTenant, provider: string): Promise<boolean> {\n const deleted = await this.#db.deleteMany('model_provider_credentials', tenantWhere(tenant, provider));\n return deleted > 0;\n }\n\n /** All credentials visible to a user: their own rows plus the org's shared rows. */\n async listCredentials(orgId: string, userId: string): Promise<CredentialRecord[]> {\n // SQL `IN` never matches NULL, so the org rows need their own query.\n const [userRows, orgRows] = await Promise.all([\n this.#db.findMany<CredentialDbRow>('model_provider_credentials', { org_id: orgId, user_id: userId }),\n this.#db.findMany<CredentialDbRow>('model_provider_credentials', { org_id: orgId, user_id: null }),\n ]);\n return [\n ...userRows.map(row => ({\n provider: row.provider,\n scope: 'user' as const,\n credential: row.data,\n updatedAt: row.updated_at,\n })),\n ...orgRows.map(row => ({\n provider: row.provider,\n scope: 'org' as const,\n credential: row.data,\n updatedAt: row.updated_at,\n })),\n ];\n }\n\n /**\n * Resolve the credential a caller should use for a provider: the user's own\n * row wins over the org's shared row.\n */\n async resolveCredential(orgId: string, userId: string, provider: string): Promise<ResolvedCredential | undefined> {\n const userRow = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', {\n org_id: orgId,\n provider,\n user_id: userId,\n });\n if (userRow) return { provider: userRow.provider, scope: 'user', credential: userRow.data };\n const orgRow = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', {\n org_id: orgId,\n provider,\n user_id: null,\n });\n if (orgRow) return { provider: orgRow.provider, scope: 'org', credential: orgRow.data };\n return undefined;\n }\n\n /**\n * Refresh the tenant's OAuth credential under the backend's atomic\n * read-modify-write so concurrent replicas don't invalidate each other's\n * rotating refresh tokens. The expiry is re-checked under the lock —\n * another replica may have refreshed already, in which case `refreshFn` is\n * skipped and the fresh credential is returned. Returns `undefined` when no\n * OAuth row exists for the tenant.\n */\n async refreshOAuth(\n tenant: CredentialTenant,\n provider: string,\n refreshFn: (current: OAuthCredential) => Promise<OAuthCredential>,\n ): Promise<OAuthCredential | undefined> {\n let result: OAuthCredential | undefined;\n await this.#db.updateAtomic<CredentialDbRow>(\n 'model_provider_credentials',\n tenantWhere(tenant, provider),\n async row => {\n if (row.data.type !== 'oauth') return null;\n const current = row.data;\n // Re-check under the lock: another replica may have refreshed while we waited.\n if (!isOAuthCredentialExpired(current)) {\n result = current;\n return null;\n }\n const next = await refreshFn(current);\n result = next;\n return { data: next, updated_at: new Date() };\n },\n );\n return result;\n }\n\n /** Persist a pending login flow started by a web route. */\n async createLoginSession(input: CreateLoginSessionInput): Promise<LoginSessionRow> {\n const inserted = await this.#db.insertOne<LoginSessionDbRow>('oauth_login_sessions', {\n session_id: input.sessionId,\n org_id: input.orgId,\n user_id: input.userId,\n provider: input.provider,\n kind: input.kind,\n pending: input.pending,\n expires_at: input.expiresAt,\n next_poll_at: input.nextPollAt ?? null,\n created_at: new Date(),\n });\n return toSessionRow(inserted);\n }\n\n /**\n * Read a pending login flow. An expired session is deleted on read (TTL\n * cleanup) and reported as absent.\n */\n async getLoginSession(sessionId: string): Promise<LoginSessionRow | undefined> {\n const row = await this.#db.findOne<LoginSessionDbRow>('oauth_login_sessions', { session_id: sessionId });\n if (!row) return undefined;\n if (row.expires_at.getTime() <= Date.now()) {\n await this.#db.deleteMany('oauth_login_sessions', { session_id: sessionId });\n return undefined;\n }\n return toSessionRow(row);\n }\n\n /**\n * Atomically claim a due login session for one upstream completion/poll attempt.\n * Returns undefined when the session is missing, expired, owned by another\n * tenant/provider, or already claimed by a concurrent request.\n */\n async claimLoginSession(\n sessionId: string,\n owner: Pick<LoginSessionRow, 'orgId' | 'userId' | 'provider' | 'kind'>,\n ): Promise<LoginSessionRow | undefined> {\n const now = Date.now();\n let claimed = false;\n const updated = await this.#db.updateAtomic<LoginSessionDbRow>(\n 'oauth_login_sessions',\n { session_id: sessionId },\n row => {\n if (\n row.org_id !== owner.orgId ||\n row.user_id !== owner.userId ||\n row.provider !== owner.provider ||\n row.kind !== owner.kind\n ) {\n return null;\n }\n if (row.expires_at.getTime() <= now) return null;\n if (row.next_poll_at && row.next_poll_at.getTime() > now) return null;\n claimed = true;\n // Park the next poll at expiry so a concurrent claim loses until the\n // claimer either touches the session forward or deletes it.\n return { next_poll_at: row.expires_at };\n },\n );\n return claimed && updated ? toSessionRow(updated) : undefined;\n }\n\n /** Update a pending flow's serialized state and/or next allowed poll time. */\n async touchLoginSession(\n sessionId: string,\n updates: { pending?: Record<string, unknown>; nextPollAt?: Date | null },\n ): Promise<void> {\n const set: Record<string, unknown> = {};\n if (updates.pending !== undefined) set.pending = updates.pending;\n if (updates.nextPollAt !== undefined) set.next_poll_at = updates.nextPollAt;\n if (Object.keys(set).length === 0) return;\n await this.#db.updateMany('oauth_login_sessions', { session_id: sessionId }, set);\n }\n\n /** Remove a pending login flow (completed, cancelled, or failed). */\n async deleteLoginSession(sessionId: string): Promise<void> {\n await this.#db.deleteMany('oauth_login_sessions', { session_id: sessionId });\n }\n}\n"],"mappings":"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"}