burnless 0.1.0
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.
- package/LICENSE +204 -0
- package/dist/ai-catalog-6AA75OA3.js +18 -0
- package/dist/ai-provider-ops-3342GI65.js +39 -0
- package/dist/chunk-BMDIVUSL.js +49 -0
- package/dist/chunk-BN3QFA6Z.js +98 -0
- package/dist/chunk-CFKTW5EF.js +50 -0
- package/dist/chunk-I5ODYEMY.js +131 -0
- package/dist/chunk-IBT7EFKB.js +226 -0
- package/dist/chunk-KSP7MJZ3.js +4075 -0
- package/dist/chunk-L24B5MJD.js +25032 -0
- package/dist/chunk-M7YWG4WP.js +32 -0
- package/dist/chunk-MHVBJUPX.js +2133 -0
- package/dist/chunk-N7IP6VPJ.js +119 -0
- package/dist/chunk-OYCC45VC.js +35 -0
- package/dist/chunk-RDP4RLLI.js +6325 -0
- package/dist/chunk-VKDF3OUE.js +27 -0
- package/dist/first-run-ai-SP5SERHZ.js +53 -0
- package/dist/index.js +1550 -0
- package/dist/index.thin.js +42 -0
- package/dist/pglite-KKF2QVEJ.js +222 -0
- package/dist/postgres-js-SQJTBL3M.js +24 -0
- package/dist/prompt-QYBNOJ26.js +14 -0
- package/dist/release-4OJIQHQH.js +32 -0
- package/dist/src-ADT5KSGE.js +611 -0
- package/dist/src-LUBD7RKO.js +14 -0
- package/package.json +40 -0
|
@@ -0,0 +1,4075 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __createRequire } from 'node:module';
|
|
3
|
+
import { fileURLToPath as __fileURLToPath } from 'node:url';
|
|
4
|
+
import { dirname as __pathDirname } from 'node:path';
|
|
5
|
+
const require = __createRequire(import.meta.url);
|
|
6
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = __pathDirname(__filename);
|
|
8
|
+
import {
|
|
9
|
+
drizzle
|
|
10
|
+
} from "./chunk-IBT7EFKB.js";
|
|
11
|
+
import {
|
|
12
|
+
src_default
|
|
13
|
+
} from "./chunk-MHVBJUPX.js";
|
|
14
|
+
import {
|
|
15
|
+
IndexedColumn,
|
|
16
|
+
PgDatabase,
|
|
17
|
+
PgTimestamp,
|
|
18
|
+
SQL,
|
|
19
|
+
and,
|
|
20
|
+
asc,
|
|
21
|
+
boolean,
|
|
22
|
+
desc,
|
|
23
|
+
entityKind,
|
|
24
|
+
eq,
|
|
25
|
+
gt,
|
|
26
|
+
inArray,
|
|
27
|
+
integer,
|
|
28
|
+
is,
|
|
29
|
+
isNull,
|
|
30
|
+
jsonb,
|
|
31
|
+
lt,
|
|
32
|
+
lte,
|
|
33
|
+
numeric,
|
|
34
|
+
or,
|
|
35
|
+
pgEnum,
|
|
36
|
+
pgTable,
|
|
37
|
+
primaryKey,
|
|
38
|
+
relations,
|
|
39
|
+
sql,
|
|
40
|
+
text,
|
|
41
|
+
timestamp
|
|
42
|
+
} from "./chunk-RDP4RLLI.js";
|
|
43
|
+
import {
|
|
44
|
+
__export
|
|
45
|
+
} from "./chunk-BMDIVUSL.js";
|
|
46
|
+
|
|
47
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.38.4_@electric-sql+pglite@0.5.2_@opentelemetry+api@1.9.0_@types+pg@8.15.6_75139cca41f10c6d94ac9ad65d4511ab/node_modules/drizzle-orm/pg-core/checks.js
|
|
48
|
+
var CheckBuilder = class {
|
|
49
|
+
constructor(name, value) {
|
|
50
|
+
this.name = name;
|
|
51
|
+
this.value = value;
|
|
52
|
+
}
|
|
53
|
+
static [entityKind] = "PgCheckBuilder";
|
|
54
|
+
brand;
|
|
55
|
+
/** @internal */
|
|
56
|
+
build(table) {
|
|
57
|
+
return new Check(table, this);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var Check = class {
|
|
61
|
+
constructor(table, builder) {
|
|
62
|
+
this.table = table;
|
|
63
|
+
this.name = builder.name;
|
|
64
|
+
this.value = builder.value;
|
|
65
|
+
}
|
|
66
|
+
static [entityKind] = "PgCheck";
|
|
67
|
+
name;
|
|
68
|
+
value;
|
|
69
|
+
};
|
|
70
|
+
function check(name, value) {
|
|
71
|
+
return new CheckBuilder(name, value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.38.4_@electric-sql+pglite@0.5.2_@opentelemetry+api@1.9.0_@types+pg@8.15.6_75139cca41f10c6d94ac9ad65d4511ab/node_modules/drizzle-orm/pg-core/indexes.js
|
|
75
|
+
var IndexBuilderOn = class {
|
|
76
|
+
constructor(unique, name) {
|
|
77
|
+
this.unique = unique;
|
|
78
|
+
this.name = name;
|
|
79
|
+
}
|
|
80
|
+
static [entityKind] = "PgIndexBuilderOn";
|
|
81
|
+
on(...columns) {
|
|
82
|
+
return new IndexBuilder(
|
|
83
|
+
columns.map((it) => {
|
|
84
|
+
if (is(it, SQL)) {
|
|
85
|
+
return it;
|
|
86
|
+
}
|
|
87
|
+
it = it;
|
|
88
|
+
const clonedIndexedColumn = new IndexedColumn(it.name, !!it.keyAsName, it.columnType, it.indexConfig);
|
|
89
|
+
it.indexConfig = JSON.parse(JSON.stringify(it.defaultConfig));
|
|
90
|
+
return clonedIndexedColumn;
|
|
91
|
+
}),
|
|
92
|
+
this.unique,
|
|
93
|
+
false,
|
|
94
|
+
this.name
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
onOnly(...columns) {
|
|
98
|
+
return new IndexBuilder(
|
|
99
|
+
columns.map((it) => {
|
|
100
|
+
if (is(it, SQL)) {
|
|
101
|
+
return it;
|
|
102
|
+
}
|
|
103
|
+
it = it;
|
|
104
|
+
const clonedIndexedColumn = new IndexedColumn(it.name, !!it.keyAsName, it.columnType, it.indexConfig);
|
|
105
|
+
it.indexConfig = it.defaultConfig;
|
|
106
|
+
return clonedIndexedColumn;
|
|
107
|
+
}),
|
|
108
|
+
this.unique,
|
|
109
|
+
true,
|
|
110
|
+
this.name
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Specify what index method to use. Choices are `btree`, `hash`, `gist`, `spgist`, `gin`, `brin`, or user-installed access methods like `bloom`. The default method is `btree.
|
|
115
|
+
*
|
|
116
|
+
* If you have the `pg_vector` extension installed in your database, you can use the `hnsw` and `ivfflat` options, which are predefined types.
|
|
117
|
+
*
|
|
118
|
+
* **You can always specify any string you want in the method, in case Drizzle doesn't have it natively in its types**
|
|
119
|
+
*
|
|
120
|
+
* @param method The name of the index method to be used
|
|
121
|
+
* @param columns
|
|
122
|
+
* @returns
|
|
123
|
+
*/
|
|
124
|
+
using(method, ...columns) {
|
|
125
|
+
return new IndexBuilder(
|
|
126
|
+
columns.map((it) => {
|
|
127
|
+
if (is(it, SQL)) {
|
|
128
|
+
return it;
|
|
129
|
+
}
|
|
130
|
+
it = it;
|
|
131
|
+
const clonedIndexedColumn = new IndexedColumn(it.name, !!it.keyAsName, it.columnType, it.indexConfig);
|
|
132
|
+
it.indexConfig = JSON.parse(JSON.stringify(it.defaultConfig));
|
|
133
|
+
return clonedIndexedColumn;
|
|
134
|
+
}),
|
|
135
|
+
this.unique,
|
|
136
|
+
true,
|
|
137
|
+
this.name,
|
|
138
|
+
method
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
var IndexBuilder = class {
|
|
143
|
+
static [entityKind] = "PgIndexBuilder";
|
|
144
|
+
/** @internal */
|
|
145
|
+
config;
|
|
146
|
+
constructor(columns, unique, only, name, method = "btree") {
|
|
147
|
+
this.config = {
|
|
148
|
+
name,
|
|
149
|
+
columns,
|
|
150
|
+
unique,
|
|
151
|
+
only,
|
|
152
|
+
method
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
concurrently() {
|
|
156
|
+
this.config.concurrently = true;
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
with(obj) {
|
|
160
|
+
this.config.with = obj;
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
where(condition) {
|
|
164
|
+
this.config.where = condition;
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
/** @internal */
|
|
168
|
+
build(table) {
|
|
169
|
+
return new Index(this.config, table);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
var Index = class {
|
|
173
|
+
static [entityKind] = "PgIndex";
|
|
174
|
+
config;
|
|
175
|
+
constructor(config, table) {
|
|
176
|
+
this.config = { ...config, table };
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
function index(name) {
|
|
180
|
+
return new IndexBuilderOn(false, name);
|
|
181
|
+
}
|
|
182
|
+
function uniqueIndex(name) {
|
|
183
|
+
return new IndexBuilderOn(true, name);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ../db/src/schema.ts
|
|
187
|
+
var schema_exports = {};
|
|
188
|
+
__export(schema_exports, {
|
|
189
|
+
accountCategoryEnum: () => accountCategoryEnum,
|
|
190
|
+
accountTypeEnum: () => accountTypeEnum,
|
|
191
|
+
accounts: () => accounts,
|
|
192
|
+
aiApiKeyModeEnum: () => aiApiKeyModeEnum,
|
|
193
|
+
aiConversations: () => aiConversations,
|
|
194
|
+
aiConversationsRelations: () => aiConversationsRelations,
|
|
195
|
+
aiDataModeEnum: () => aiDataModeEnum,
|
|
196
|
+
aiFeatureFlags: () => aiFeatureFlags,
|
|
197
|
+
aiFeatureFlagsRelations: () => aiFeatureFlagsRelations,
|
|
198
|
+
aiInsightCache: () => aiInsightCache,
|
|
199
|
+
aiInsightCacheRelations: () => aiInsightCacheRelations,
|
|
200
|
+
aiInsightCacheTypeEnum: () => aiInsightCacheTypeEnum,
|
|
201
|
+
aiMessageRoleEnum: () => aiMessageRoleEnum,
|
|
202
|
+
aiMessages: () => aiMessages,
|
|
203
|
+
aiMessagesRelations: () => aiMessagesRelations,
|
|
204
|
+
aiPendingActionKindEnum: () => aiPendingActionKindEnum,
|
|
205
|
+
aiPendingActions: () => aiPendingActions,
|
|
206
|
+
aiPermissionDefaults: () => aiPermissionDefaults,
|
|
207
|
+
aiPermissionModeEnum: () => aiPermissionModeEnum,
|
|
208
|
+
aiProviderKindEnum: () => aiProviderKindEnum,
|
|
209
|
+
aiProviderModelSourceEnum: () => aiProviderModelSourceEnum,
|
|
210
|
+
aiProviderModels: () => aiProviderModels,
|
|
211
|
+
aiProviderModelsRelations: () => aiProviderModelsRelations,
|
|
212
|
+
aiProviders: () => aiProviders,
|
|
213
|
+
aiProvidersRelations: () => aiProvidersRelations,
|
|
214
|
+
aiToolAuditLogStatusEnum: () => aiToolAuditLogStatusEnum,
|
|
215
|
+
aiToolAuditLogs: () => aiToolAuditLogs,
|
|
216
|
+
aiToolAuditLogsRelations: () => aiToolAuditLogsRelations,
|
|
217
|
+
aiToolPermissionDecisionEnum: () => aiToolPermissionDecisionEnum,
|
|
218
|
+
aiUsageLogs: () => aiUsageLogs,
|
|
219
|
+
aiWriteModeEnum: () => aiWriteModeEnum,
|
|
220
|
+
apiTokens: () => apiTokens,
|
|
221
|
+
auditActionEnum: () => auditActionEnum,
|
|
222
|
+
auditEntityTypeEnum: () => auditEntityTypeEnum,
|
|
223
|
+
bonusTypeEnum: () => bonusTypeEnum,
|
|
224
|
+
bonuses: () => bonuses,
|
|
225
|
+
bonusesRelations: () => bonusesRelations,
|
|
226
|
+
businessModelEnum: () => businessModelEnum,
|
|
227
|
+
companies: () => companies,
|
|
228
|
+
companiesRelations: () => companiesRelations,
|
|
229
|
+
companyMembers: () => companyMembers,
|
|
230
|
+
companyMembersRelations: () => companyMembersRelations,
|
|
231
|
+
companyStageEnum: () => companyStageEnum,
|
|
232
|
+
consentPurposeEnum: () => consentPurposeEnum,
|
|
233
|
+
dashboardModeEnum: () => dashboardModeEnum,
|
|
234
|
+
dashboardPreferences: () => dashboardPreferences,
|
|
235
|
+
dashboardPreferencesRelations: () => dashboardPreferencesRelations,
|
|
236
|
+
dataRegionEnum: () => dataRegionEnum,
|
|
237
|
+
departments: () => departments,
|
|
238
|
+
departmentsRelations: () => departmentsRelations,
|
|
239
|
+
equityGrantTypeEnum: () => equityGrantTypeEnum,
|
|
240
|
+
equityGrants: () => equityGrants,
|
|
241
|
+
equityGrantsRelations: () => equityGrantsRelations,
|
|
242
|
+
expenseFrequencyEnum: () => expenseFrequencyEnum,
|
|
243
|
+
exportLogs: () => exportLogs,
|
|
244
|
+
financialAccounts: () => financialAccounts,
|
|
245
|
+
financialAccountsRelations: () => financialAccountsRelations,
|
|
246
|
+
financialAuditLogs: () => financialAuditLogs,
|
|
247
|
+
financialAuditLogsRelations: () => financialAuditLogsRelations,
|
|
248
|
+
forecastLines: () => forecastLines,
|
|
249
|
+
forecastLinesRelations: () => forecastLinesRelations,
|
|
250
|
+
forecastMethodEnum: () => forecastMethodEnum,
|
|
251
|
+
forecastValues: () => forecastValues,
|
|
252
|
+
forecastValuesRelations: () => forecastValuesRelations,
|
|
253
|
+
fundingRoundInvestors: () => fundingRoundInvestors,
|
|
254
|
+
fundingRoundInvestorsRelations: () => fundingRoundInvestorsRelations,
|
|
255
|
+
fundingRoundTypeEnum: () => fundingRoundTypeEnum,
|
|
256
|
+
fundingRounds: () => fundingRounds,
|
|
257
|
+
fundingRoundsRelations: () => fundingRoundsRelations,
|
|
258
|
+
headcountEmployeeTypeEnum: () => headcountEmployeeTypeEnum,
|
|
259
|
+
headcountPlans: () => headcountPlans,
|
|
260
|
+
headcountPlansRelations: () => headcountPlansRelations,
|
|
261
|
+
importBatchStatusEnum: () => importBatchStatusEnum,
|
|
262
|
+
importBatches: () => importBatches,
|
|
263
|
+
importBatchesRelations: () => importBatchesRelations,
|
|
264
|
+
insightInvalidations: () => insightInvalidations,
|
|
265
|
+
integrationStatusEnum: () => integrationStatusEnum,
|
|
266
|
+
integrationTypeEnum: () => integrationTypeEnum,
|
|
267
|
+
integrations: () => integrations,
|
|
268
|
+
integrationsRelations: () => integrationsRelations,
|
|
269
|
+
inviteCodeRedemptions: () => inviteCodeRedemptions,
|
|
270
|
+
inviteCodeRedemptionsRelations: () => inviteCodeRedemptionsRelations,
|
|
271
|
+
inviteCodeTypeEnum: () => inviteCodeTypeEnum,
|
|
272
|
+
inviteCodes: () => inviteCodes,
|
|
273
|
+
inviteCodesRelations: () => inviteCodesRelations,
|
|
274
|
+
mcpAuthTypeEnum: () => mcpAuthTypeEnum,
|
|
275
|
+
mcpConnectionStatusEnum: () => mcpConnectionStatusEnum,
|
|
276
|
+
mcpConnections: () => mcpConnections,
|
|
277
|
+
mcpConnectionsRelations: () => mcpConnectionsRelations,
|
|
278
|
+
mcpCredentials: () => mcpCredentials,
|
|
279
|
+
mcpCredentialsRelations: () => mcpCredentialsRelations,
|
|
280
|
+
mcpOwnerScopeEnum: () => mcpOwnerScopeEnum,
|
|
281
|
+
mcpToolPermEnum: () => mcpToolPermEnum,
|
|
282
|
+
mcpToolPrefs: () => mcpToolPrefs,
|
|
283
|
+
mcpToolPrefsRelations: () => mcpToolPrefsRelations,
|
|
284
|
+
mcpTransportEnum: () => mcpTransportEnum,
|
|
285
|
+
memberRoleEnum: () => memberRoleEnum,
|
|
286
|
+
merchantCategoryMappings: () => merchantCategoryMappings,
|
|
287
|
+
merchantCategoryMappingsRelations: () => merchantCategoryMappingsRelations,
|
|
288
|
+
metricCategoryEnum: () => metricCategoryEnum,
|
|
289
|
+
metrics: () => metrics,
|
|
290
|
+
metricsRelations: () => metricsRelations,
|
|
291
|
+
notificationSeverityEnum: () => notificationSeverityEnum,
|
|
292
|
+
notifications: () => notifications,
|
|
293
|
+
oauthAuthCodes: () => oauthAuthCodes,
|
|
294
|
+
oauthClients: () => oauthClients,
|
|
295
|
+
oauthTokens: () => oauthTokens,
|
|
296
|
+
optionPools: () => optionPools,
|
|
297
|
+
optionPoolsRelations: () => optionPoolsRelations,
|
|
298
|
+
privacyConsents: () => privacyConsents,
|
|
299
|
+
privacyConsentsRelations: () => privacyConsentsRelations,
|
|
300
|
+
quickActionModeEnum: () => quickActionModeEnum,
|
|
301
|
+
revenueStreamTypeEnum: () => revenueStreamTypeEnum,
|
|
302
|
+
revenueStreams: () => revenueStreams,
|
|
303
|
+
revenueStreamsRelations: () => revenueStreamsRelations,
|
|
304
|
+
salaryChanges: () => salaryChanges,
|
|
305
|
+
salaryChangesRelations: () => salaryChangesRelations,
|
|
306
|
+
scenarioOverrideActionEnum: () => scenarioOverrideActionEnum,
|
|
307
|
+
scenarioOverrides: () => scenarioOverrides,
|
|
308
|
+
scenarioOverridesRelations: () => scenarioOverridesRelations,
|
|
309
|
+
scenarioSourceEnum: () => scenarioSourceEnum,
|
|
310
|
+
scenarioStatusEnum: () => scenarioStatusEnum,
|
|
311
|
+
scenarios: () => scenarios,
|
|
312
|
+
scenariosRelations: () => scenariosRelations,
|
|
313
|
+
scheduledJobActionKindEnum: () => scheduledJobActionKindEnum,
|
|
314
|
+
scheduledJobNotifyPolicyEnum: () => scheduledJobNotifyPolicyEnum,
|
|
315
|
+
scheduledJobRunStatusEnum: () => scheduledJobRunStatusEnum,
|
|
316
|
+
scheduledJobRunTriggerEnum: () => scheduledJobRunTriggerEnum,
|
|
317
|
+
scheduledJobRuns: () => scheduledJobRuns,
|
|
318
|
+
scheduledJobStatusEnum: () => scheduledJobStatusEnum,
|
|
319
|
+
scheduledJobs: () => scheduledJobs,
|
|
320
|
+
sessions: () => sessions,
|
|
321
|
+
shareClassTypeEnum: () => shareClassTypeEnum,
|
|
322
|
+
shareClasses: () => shareClasses,
|
|
323
|
+
shareClassesRelations: () => shareClassesRelations,
|
|
324
|
+
transactionSourceEnum: () => transactionSourceEnum,
|
|
325
|
+
transactions: () => transactions,
|
|
326
|
+
transactionsRelations: () => transactionsRelations,
|
|
327
|
+
userPreferences: () => userPreferences,
|
|
328
|
+
userPreferencesRelations: () => userPreferencesRelations,
|
|
329
|
+
users: () => users,
|
|
330
|
+
usersRelations: () => usersRelations,
|
|
331
|
+
verificationTokens: () => verificationTokens,
|
|
332
|
+
weeklyDigests: () => weeklyDigests,
|
|
333
|
+
weeklyDigestsRelations: () => weeklyDigestsRelations
|
|
334
|
+
});
|
|
335
|
+
var companyStageEnum = pgEnum("company_stage", [
|
|
336
|
+
"pre_seed",
|
|
337
|
+
"seed",
|
|
338
|
+
"series_a",
|
|
339
|
+
"series_b",
|
|
340
|
+
"series_c_plus",
|
|
341
|
+
"bootstrapped"
|
|
342
|
+
]);
|
|
343
|
+
var businessModelEnum = pgEnum("business_model", [
|
|
344
|
+
"saas",
|
|
345
|
+
"marketplace",
|
|
346
|
+
"ecommerce",
|
|
347
|
+
"services",
|
|
348
|
+
"hardware",
|
|
349
|
+
"other"
|
|
350
|
+
]);
|
|
351
|
+
var accountTypeEnum = pgEnum("account_type", [
|
|
352
|
+
"income",
|
|
353
|
+
"expense",
|
|
354
|
+
"asset",
|
|
355
|
+
"liability",
|
|
356
|
+
"equity"
|
|
357
|
+
]);
|
|
358
|
+
var accountCategoryEnum = pgEnum("account_category", [
|
|
359
|
+
"revenue",
|
|
360
|
+
"cogs",
|
|
361
|
+
"operating_expense",
|
|
362
|
+
"other_income",
|
|
363
|
+
"other_expense",
|
|
364
|
+
"asset",
|
|
365
|
+
"liability",
|
|
366
|
+
"equity"
|
|
367
|
+
]);
|
|
368
|
+
var transactionSourceEnum = pgEnum("transaction_source", [
|
|
369
|
+
"manual",
|
|
370
|
+
"import",
|
|
371
|
+
"integration",
|
|
372
|
+
"forecast"
|
|
373
|
+
]);
|
|
374
|
+
var scenarioSourceEnum = pgEnum("scenario_source", [
|
|
375
|
+
"blank",
|
|
376
|
+
"ai",
|
|
377
|
+
"template",
|
|
378
|
+
"clone",
|
|
379
|
+
"backup"
|
|
380
|
+
]);
|
|
381
|
+
var scenarioStatusEnum = pgEnum("scenario_status", [
|
|
382
|
+
"active",
|
|
383
|
+
"promoted",
|
|
384
|
+
"archived"
|
|
385
|
+
]);
|
|
386
|
+
var scenarioOverrideActionEnum = pgEnum("scenario_override_action", [
|
|
387
|
+
"create",
|
|
388
|
+
"modify",
|
|
389
|
+
"delete"
|
|
390
|
+
]);
|
|
391
|
+
var forecastMethodEnum = pgEnum("forecast_method", [
|
|
392
|
+
"fixed",
|
|
393
|
+
"growth_rate",
|
|
394
|
+
"per_unit",
|
|
395
|
+
"percentage_of",
|
|
396
|
+
"custom_formula"
|
|
397
|
+
]);
|
|
398
|
+
var expenseFrequencyEnum = pgEnum("expense_frequency", [
|
|
399
|
+
"monthly",
|
|
400
|
+
"quarterly",
|
|
401
|
+
"annual"
|
|
402
|
+
]);
|
|
403
|
+
var memberRoleEnum = pgEnum("member_role", [
|
|
404
|
+
"owner",
|
|
405
|
+
"admin",
|
|
406
|
+
"editor",
|
|
407
|
+
"viewer"
|
|
408
|
+
]);
|
|
409
|
+
var integrationTypeEnum = pgEnum("integration_type", [
|
|
410
|
+
"quickbooks",
|
|
411
|
+
"xero",
|
|
412
|
+
"freshbooks",
|
|
413
|
+
"plaid",
|
|
414
|
+
"mercury",
|
|
415
|
+
"gusto",
|
|
416
|
+
"stripe"
|
|
417
|
+
]);
|
|
418
|
+
var integrationStatusEnum = pgEnum("integration_status", [
|
|
419
|
+
"active",
|
|
420
|
+
"disconnected",
|
|
421
|
+
"error"
|
|
422
|
+
]);
|
|
423
|
+
var fundingRoundTypeEnum = pgEnum("funding_round_type", [
|
|
424
|
+
"pre_seed",
|
|
425
|
+
"seed",
|
|
426
|
+
"series_a",
|
|
427
|
+
"series_b",
|
|
428
|
+
"series_c_plus",
|
|
429
|
+
"debt",
|
|
430
|
+
"grant",
|
|
431
|
+
"safe",
|
|
432
|
+
"convertible"
|
|
433
|
+
]);
|
|
434
|
+
var revenueStreamTypeEnum = pgEnum("revenue_stream_type", [
|
|
435
|
+
"subscription",
|
|
436
|
+
"one_time",
|
|
437
|
+
"usage_based",
|
|
438
|
+
"services",
|
|
439
|
+
"marketplace",
|
|
440
|
+
"ecommerce",
|
|
441
|
+
"hardware"
|
|
442
|
+
]);
|
|
443
|
+
var metricCategoryEnum = pgEnum("metric_category", [
|
|
444
|
+
"financial",
|
|
445
|
+
"saas",
|
|
446
|
+
"growth",
|
|
447
|
+
"efficiency",
|
|
448
|
+
"custom"
|
|
449
|
+
]);
|
|
450
|
+
var aiMessageRoleEnum = pgEnum("ai_message_role", [
|
|
451
|
+
"user",
|
|
452
|
+
"assistant",
|
|
453
|
+
"system"
|
|
454
|
+
]);
|
|
455
|
+
var aiDataModeEnum = pgEnum("ai_data_mode", [
|
|
456
|
+
"full",
|
|
457
|
+
"show_cached",
|
|
458
|
+
"hide_all"
|
|
459
|
+
]);
|
|
460
|
+
var aiWriteModeEnum = pgEnum("ai_write_mode", [
|
|
461
|
+
"full",
|
|
462
|
+
"confirm",
|
|
463
|
+
"read_only"
|
|
464
|
+
]);
|
|
465
|
+
var aiProviderKindEnum = pgEnum("ai_provider_kind", [
|
|
466
|
+
"anthropic",
|
|
467
|
+
"openai",
|
|
468
|
+
"openrouter",
|
|
469
|
+
"ollama",
|
|
470
|
+
"google",
|
|
471
|
+
"mistral",
|
|
472
|
+
"groq",
|
|
473
|
+
"openai-compatible"
|
|
474
|
+
]);
|
|
475
|
+
var aiApiKeyModeEnum = pgEnum("ai_api_key_mode", ["managed", "user_provided", "none"]);
|
|
476
|
+
var aiProviderModelSourceEnum = pgEnum("ai_provider_model_source", ["fetched", "manual", "preset"]);
|
|
477
|
+
var aiPermissionModeEnum = pgEnum("ai_permission_mode", [
|
|
478
|
+
"ask",
|
|
479
|
+
"session",
|
|
480
|
+
"always"
|
|
481
|
+
]);
|
|
482
|
+
var aiToolPermissionDecisionEnum = pgEnum("ai_tool_permission_decision", [
|
|
483
|
+
"auto",
|
|
484
|
+
"granted_once",
|
|
485
|
+
"granted_session",
|
|
486
|
+
"denied"
|
|
487
|
+
]);
|
|
488
|
+
var headcountEmployeeTypeEnum = pgEnum("headcount_employee_type", [
|
|
489
|
+
"full_time",
|
|
490
|
+
"part_time",
|
|
491
|
+
"contractor"
|
|
492
|
+
]);
|
|
493
|
+
var notificationSeverityEnum = pgEnum("notification_severity", [
|
|
494
|
+
"info",
|
|
495
|
+
"success",
|
|
496
|
+
"warning",
|
|
497
|
+
"error"
|
|
498
|
+
]);
|
|
499
|
+
var mcpTransportEnum = pgEnum("mcp_transport", ["streamable_http", "stdio"]);
|
|
500
|
+
var mcpOwnerScopeEnum = pgEnum("mcp_owner_scope", ["company", "personal"]);
|
|
501
|
+
var mcpAuthTypeEnum = pgEnum("mcp_auth_type", ["oauth", "pat", "none"]);
|
|
502
|
+
var mcpConnectionStatusEnum = pgEnum("mcp_connection_status", [
|
|
503
|
+
"pending",
|
|
504
|
+
"connected",
|
|
505
|
+
"needs_auth",
|
|
506
|
+
"error",
|
|
507
|
+
"disabled"
|
|
508
|
+
]);
|
|
509
|
+
var mcpToolPermEnum = pgEnum("mcp_tool_perm", ["read", "write", "delete"]);
|
|
510
|
+
var users = pgTable("users", {
|
|
511
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
512
|
+
name: text("name"),
|
|
513
|
+
email: text("email").unique().notNull(),
|
|
514
|
+
emailVerified: timestamp("email_verified", { mode: "date" }),
|
|
515
|
+
image: text("image"),
|
|
516
|
+
passwordHash: text("password_hash"),
|
|
517
|
+
twoFactorEnabled: boolean("two_factor_enabled").default(false).notNull(),
|
|
518
|
+
twoFactorSecret: text("two_factor_secret"),
|
|
519
|
+
twoFactorBackupCodes: text("two_factor_backup_codes"),
|
|
520
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
521
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
522
|
+
});
|
|
523
|
+
var accounts = pgTable(
|
|
524
|
+
"accounts",
|
|
525
|
+
{
|
|
526
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
527
|
+
type: text("type").notNull(),
|
|
528
|
+
provider: text("provider").notNull(),
|
|
529
|
+
providerAccountId: text("provider_account_id").notNull(),
|
|
530
|
+
refresh_token: text("refresh_token"),
|
|
531
|
+
access_token: text("access_token"),
|
|
532
|
+
expires_at: integer("expires_at"),
|
|
533
|
+
token_type: text("token_type"),
|
|
534
|
+
scope: text("scope"),
|
|
535
|
+
id_token: text("id_token"),
|
|
536
|
+
session_state: text("session_state")
|
|
537
|
+
},
|
|
538
|
+
(table) => [
|
|
539
|
+
primaryKey({ columns: [table.provider, table.providerAccountId] }),
|
|
540
|
+
index("accounts_user_idx").on(table.userId)
|
|
541
|
+
]
|
|
542
|
+
);
|
|
543
|
+
var sessions = pgTable(
|
|
544
|
+
"sessions",
|
|
545
|
+
{
|
|
546
|
+
sessionToken: text("session_token").primaryKey(),
|
|
547
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
548
|
+
expires: timestamp("expires", { mode: "date" }).notNull()
|
|
549
|
+
},
|
|
550
|
+
(table) => [
|
|
551
|
+
index("sessions_user_idx").on(table.userId)
|
|
552
|
+
]
|
|
553
|
+
);
|
|
554
|
+
var verificationTokens = pgTable(
|
|
555
|
+
"verification_tokens",
|
|
556
|
+
{
|
|
557
|
+
identifier: text("identifier").notNull(),
|
|
558
|
+
token: text("token").notNull(),
|
|
559
|
+
expires: timestamp("expires", { mode: "date" }).notNull()
|
|
560
|
+
},
|
|
561
|
+
(table) => [
|
|
562
|
+
primaryKey({ columns: [table.identifier, table.token] })
|
|
563
|
+
]
|
|
564
|
+
);
|
|
565
|
+
var dataRegionEnum = pgEnum("data_region", [
|
|
566
|
+
"us-east",
|
|
567
|
+
"eu-west",
|
|
568
|
+
"ap-south"
|
|
569
|
+
]);
|
|
570
|
+
var companies = pgTable("companies", {
|
|
571
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
572
|
+
name: text("name").notNull(),
|
|
573
|
+
stage: companyStageEnum("stage").notNull().default("pre_seed"),
|
|
574
|
+
businessModel: businessModelEnum("business_model").notNull().default("saas"),
|
|
575
|
+
industry: text("industry"),
|
|
576
|
+
foundedDate: timestamp("founded_date", { mode: "date" }),
|
|
577
|
+
fiscalYearEnd: integer("fiscal_year_end").notNull().default(12),
|
|
578
|
+
currency: text("currency").notNull().default("USD"),
|
|
579
|
+
locale: text("locale").notNull().default("en-US"),
|
|
580
|
+
timezone: text("timezone").notNull().default("America/New_York"),
|
|
581
|
+
region: dataRegionEnum("region").notNull().default("us-east"),
|
|
582
|
+
ownerId: text("owner_id").notNull().references(() => users.id),
|
|
583
|
+
billingProvider: text("billing_provider"),
|
|
584
|
+
billingCustomerId: text("billing_customer_id"),
|
|
585
|
+
billingSubscriptionId: text("billing_subscription_id"),
|
|
586
|
+
billingPlan: text("billing_plan").default("free"),
|
|
587
|
+
benefitsRates: jsonb("benefits_rates").notNull().default({}),
|
|
588
|
+
foundersOwnershipPercent: numeric("founders_ownership_percent", { precision: 7, scale: 4 }).notNull().default("100.0000"),
|
|
589
|
+
/** B8 kill switch (expose spec §3): false ⇒ every inbound /mcp call 403s.
|
|
590
|
+
* Tokens stay intact; flipping back on restores access. Default true. */
|
|
591
|
+
mcpServerEnabled: boolean("mcp_server_enabled").notNull().default(true),
|
|
592
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
593
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
594
|
+
});
|
|
595
|
+
var companyMembers = pgTable(
|
|
596
|
+
"company_members",
|
|
597
|
+
{
|
|
598
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
599
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
600
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
601
|
+
role: memberRoleEnum("role").notNull().default("viewer"),
|
|
602
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
603
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
604
|
+
},
|
|
605
|
+
(table) => [
|
|
606
|
+
uniqueIndex("company_member_unique").on(table.companyId, table.userId),
|
|
607
|
+
index("company_member_user_idx").on(table.userId)
|
|
608
|
+
]
|
|
609
|
+
);
|
|
610
|
+
var departments = pgTable(
|
|
611
|
+
"departments",
|
|
612
|
+
{
|
|
613
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
614
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
615
|
+
name: text("name").notNull(),
|
|
616
|
+
parentId: text("parent_id"),
|
|
617
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
618
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
619
|
+
},
|
|
620
|
+
(table) => [
|
|
621
|
+
index("departments_company_idx").on(table.companyId)
|
|
622
|
+
]
|
|
623
|
+
);
|
|
624
|
+
var financialAccounts = pgTable(
|
|
625
|
+
"financial_accounts",
|
|
626
|
+
{
|
|
627
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
628
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
629
|
+
name: text("name").notNull(),
|
|
630
|
+
type: accountTypeEnum("type").notNull(),
|
|
631
|
+
category: accountCategoryEnum("category").notNull(),
|
|
632
|
+
parentId: text("parent_id"),
|
|
633
|
+
isSystem: boolean("is_system").notNull().default(false),
|
|
634
|
+
sortOrder: integer("sort_order").notNull().default(0),
|
|
635
|
+
// When true, transactions on this account ARE personnel cost that the
|
|
636
|
+
// headcount plan also models. The compute layer then uses these actuals in
|
|
637
|
+
// months where they exist and suppresses the headcount-plan cost for those
|
|
638
|
+
// months, so payroll isn't double-counted (actuals in closed months, plan
|
|
639
|
+
// in forecast months). See `reconcileHeadcountWithActuals`.
|
|
640
|
+
coversHeadcount: boolean("covers_headcount").notNull().default(false),
|
|
641
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
642
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
643
|
+
},
|
|
644
|
+
(table) => [
|
|
645
|
+
index("financial_accounts_company_idx").on(table.companyId),
|
|
646
|
+
index("financial_accounts_parent_idx").on(table.parentId)
|
|
647
|
+
]
|
|
648
|
+
);
|
|
649
|
+
var transactions = pgTable(
|
|
650
|
+
"transactions",
|
|
651
|
+
{
|
|
652
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
653
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
654
|
+
accountId: text("account_id").notNull().references(() => financialAccounts.id, { onDelete: "cascade" }),
|
|
655
|
+
date: timestamp("date", { mode: "date" }).notNull(),
|
|
656
|
+
amount: numeric("amount", { precision: 18, scale: 2 }).notNull(),
|
|
657
|
+
description: text("description"),
|
|
658
|
+
vendor: text("vendor"),
|
|
659
|
+
notes: text("notes"),
|
|
660
|
+
source: transactionSourceEnum("source").notNull().default("manual"),
|
|
661
|
+
externalId: text("external_id"),
|
|
662
|
+
importBatchId: text("import_batch_id"),
|
|
663
|
+
metadata: jsonb("metadata"),
|
|
664
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
665
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
666
|
+
},
|
|
667
|
+
(table) => [
|
|
668
|
+
index("transactions_company_date_idx").on(table.companyId, table.date),
|
|
669
|
+
index("transactions_account_idx").on(table.accountId),
|
|
670
|
+
uniqueIndex("transactions_external_id_idx").on(
|
|
671
|
+
table.companyId,
|
|
672
|
+
table.externalId
|
|
673
|
+
),
|
|
674
|
+
index("transactions_batch_idx").on(table.importBatchId)
|
|
675
|
+
]
|
|
676
|
+
);
|
|
677
|
+
var importBatchStatusEnum = pgEnum("import_batch_status", [
|
|
678
|
+
"pending",
|
|
679
|
+
"processing",
|
|
680
|
+
"completed",
|
|
681
|
+
"rolled_back",
|
|
682
|
+
"failed"
|
|
683
|
+
]);
|
|
684
|
+
var importBatches = pgTable(
|
|
685
|
+
"import_batches",
|
|
686
|
+
{
|
|
687
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
688
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
689
|
+
fileName: text("file_name").notNull(),
|
|
690
|
+
status: importBatchStatusEnum("status").notNull().default("pending"),
|
|
691
|
+
totalRows: integer("total_rows").notNull().default(0),
|
|
692
|
+
importedCount: integer("imported_count").notNull().default(0),
|
|
693
|
+
skippedCount: integer("skipped_count").notNull().default(0),
|
|
694
|
+
errorCount: integer("error_count").notNull().default(0),
|
|
695
|
+
accountId: text("account_id").references(() => financialAccounts.id),
|
|
696
|
+
columnMapping: jsonb("column_mapping"),
|
|
697
|
+
errors: jsonb("errors"),
|
|
698
|
+
metadata: jsonb("metadata"),
|
|
699
|
+
rolledBackAt: timestamp("rolled_back_at", { mode: "date" }),
|
|
700
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
701
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
702
|
+
},
|
|
703
|
+
(table) => [
|
|
704
|
+
index("import_batches_company_idx").on(table.companyId),
|
|
705
|
+
index("import_batches_account_idx").on(table.accountId)
|
|
706
|
+
]
|
|
707
|
+
);
|
|
708
|
+
var scenarios = pgTable(
|
|
709
|
+
"scenarios",
|
|
710
|
+
{
|
|
711
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
712
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
713
|
+
name: text("name").notNull(),
|
|
714
|
+
description: text("description"),
|
|
715
|
+
source: scenarioSourceEnum("source").notNull().default("blank"),
|
|
716
|
+
status: scenarioStatusEnum("status").notNull().default("active"),
|
|
717
|
+
color: text("color"),
|
|
718
|
+
sourceScenarioId: text("source_scenario_id"),
|
|
719
|
+
aiConversationId: text("ai_conversation_id"),
|
|
720
|
+
promotedAt: timestamp("promoted_at", { mode: "date" }),
|
|
721
|
+
autoDeleteAt: timestamp("auto_delete_at", { mode: "date" }),
|
|
722
|
+
deletedAt: timestamp("deleted_at", { mode: "date" }),
|
|
723
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
724
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
725
|
+
},
|
|
726
|
+
(table) => [
|
|
727
|
+
index("scenarios_company_idx").on(table.companyId)
|
|
728
|
+
]
|
|
729
|
+
);
|
|
730
|
+
var scenarioOverrides = pgTable(
|
|
731
|
+
"scenario_overrides",
|
|
732
|
+
{
|
|
733
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
734
|
+
scenarioId: text("scenario_id").notNull().references(() => scenarios.id, { onDelete: "cascade" }),
|
|
735
|
+
entityType: text("entity_type").notNull(),
|
|
736
|
+
entityId: text("entity_id").notNull(),
|
|
737
|
+
action: scenarioOverrideActionEnum("action").notNull(),
|
|
738
|
+
data: jsonb("data"),
|
|
739
|
+
originalData: jsonb("original_data"),
|
|
740
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
741
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
742
|
+
},
|
|
743
|
+
(table) => [
|
|
744
|
+
uniqueIndex("scenario_overrides_unique").on(table.scenarioId, table.entityType, table.entityId),
|
|
745
|
+
index("scenario_overrides_scenario_type").on(table.scenarioId, table.entityType)
|
|
746
|
+
]
|
|
747
|
+
);
|
|
748
|
+
var forecastLines = pgTable(
|
|
749
|
+
"forecast_lines",
|
|
750
|
+
{
|
|
751
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
752
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
753
|
+
accountId: text("account_id").notNull().references(() => financialAccounts.id, { onDelete: "cascade" }),
|
|
754
|
+
method: forecastMethodEnum("method").notNull().default("fixed"),
|
|
755
|
+
parameters: jsonb("parameters").notNull().default({}),
|
|
756
|
+
startDate: timestamp("start_date", { mode: "date" }).notNull(),
|
|
757
|
+
endDate: timestamp("end_date", { mode: "date" }),
|
|
758
|
+
/**
|
|
759
|
+
* Phase 4 §4.1 — stable identifier a `custom_formula` expression can
|
|
760
|
+
* reference (`CloudCosts * 2`). Sanitized `^[A-Za-z_][A-Za-z0-9_]*$`;
|
|
761
|
+
* unique per company when non-null (see partial index below).
|
|
762
|
+
*/
|
|
763
|
+
name: text("name"),
|
|
764
|
+
// ── Phase 1 additions (§2.C) ─────────────────────────────────────────
|
|
765
|
+
notes: text("notes"),
|
|
766
|
+
vendor: text("vendor"),
|
|
767
|
+
/**
|
|
768
|
+
* Explicit per-line expense category override (set in the expense form).
|
|
769
|
+
* NULL = derive automatically (merchant rules → account → "Uncategorized").
|
|
770
|
+
* A non-null value WINS over derivation in compute-expenses deriveSubcategory.
|
|
771
|
+
*/
|
|
772
|
+
subcategory: text("subcategory"),
|
|
773
|
+
departmentId: text("department_id").references(() => departments.id, {
|
|
774
|
+
onDelete: "set null"
|
|
775
|
+
}),
|
|
776
|
+
frequency: expenseFrequencyEnum("frequency").notNull().default("monthly"),
|
|
777
|
+
isOneTime: boolean("is_one_time").notNull().default(false),
|
|
778
|
+
/**
|
|
779
|
+
* Tri-state recurring flag (Phase 1 §1.5 anomaly refactor).
|
|
780
|
+
* NULL = user has not declared; UI shows suggestion based on variance.
|
|
781
|
+
* true/false = explicit user choice.
|
|
782
|
+
*/
|
|
783
|
+
isRecurring: boolean("is_recurring"),
|
|
784
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
785
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
786
|
+
},
|
|
787
|
+
(table) => [
|
|
788
|
+
index("forecast_lines_company_idx").on(table.companyId),
|
|
789
|
+
// Non-unique: Phase 1 consolidated form treats each forecast line as an
|
|
790
|
+
// independent expense (vendor / notes / department / frequency / isOneTime),
|
|
791
|
+
// so multiple lines per account are expected.
|
|
792
|
+
index("forecast_lines_company_account_idx").on(
|
|
793
|
+
table.companyId,
|
|
794
|
+
table.accountId
|
|
795
|
+
),
|
|
796
|
+
index("forecast_lines_company_department_idx").on(
|
|
797
|
+
table.companyId,
|
|
798
|
+
table.departmentId
|
|
799
|
+
),
|
|
800
|
+
index("forecast_lines_vendor_idx").on(table.companyId, table.vendor),
|
|
801
|
+
// Phase 4 §4.1 — line names are unique per company when set (NULL allowed).
|
|
802
|
+
uniqueIndex("forecast_lines_company_name_idx").on(table.companyId, table.name).where(sql`${table.name} IS NOT NULL`)
|
|
803
|
+
]
|
|
804
|
+
);
|
|
805
|
+
var forecastValues = pgTable(
|
|
806
|
+
"forecast_values",
|
|
807
|
+
{
|
|
808
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
809
|
+
forecastLineId: text("forecast_line_id").notNull().references(() => forecastLines.id, { onDelete: "cascade" }),
|
|
810
|
+
month: timestamp("month", { mode: "date" }).notNull(),
|
|
811
|
+
amount: numeric("amount", { precision: 18, scale: 2 }).notNull(),
|
|
812
|
+
isOverride: boolean("is_override").notNull().default(false),
|
|
813
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
814
|
+
},
|
|
815
|
+
(table) => [
|
|
816
|
+
index("forecast_values_line_idx").on(table.forecastLineId),
|
|
817
|
+
index("forecast_values_month_idx").on(table.month),
|
|
818
|
+
uniqueIndex("forecast_values_line_month_idx").on(
|
|
819
|
+
table.forecastLineId,
|
|
820
|
+
table.month
|
|
821
|
+
)
|
|
822
|
+
]
|
|
823
|
+
);
|
|
824
|
+
var headcountPlans = pgTable(
|
|
825
|
+
"headcount_plans",
|
|
826
|
+
{
|
|
827
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
828
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
829
|
+
departmentId: text("department_id").notNull().references(() => departments.id, { onDelete: "cascade" }),
|
|
830
|
+
title: text("title").notNull(),
|
|
831
|
+
name: text("name"),
|
|
832
|
+
employeeType: headcountEmployeeTypeEnum("employee_type").notNull().default("full_time"),
|
|
833
|
+
count: numeric("count", { precision: 5, scale: 2 }).notNull().default("1.00"),
|
|
834
|
+
salary: numeric("salary", { precision: 12, scale: 2 }).notNull(),
|
|
835
|
+
hourlyRate: numeric("hourly_rate", { precision: 12, scale: 2 }),
|
|
836
|
+
hoursPerWeek: numeric("hours_per_week", { precision: 5, scale: 2 }),
|
|
837
|
+
startDate: timestamp("start_date", { mode: "date" }).notNull(),
|
|
838
|
+
endDate: timestamp("end_date", { mode: "date" }),
|
|
839
|
+
benefitsRate: numeric("benefits_rate", { precision: 5, scale: 4 }).notNull().default("0.20"),
|
|
840
|
+
parameters: jsonb("parameters").notNull().default({}),
|
|
841
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
842
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
843
|
+
},
|
|
844
|
+
(table) => [
|
|
845
|
+
index("headcount_plans_company_idx").on(table.companyId),
|
|
846
|
+
index("headcount_plans_department_idx").on(table.departmentId)
|
|
847
|
+
]
|
|
848
|
+
);
|
|
849
|
+
var equityGrantTypeEnum = pgEnum("equity_grant_type", [
|
|
850
|
+
"iso",
|
|
851
|
+
"nso",
|
|
852
|
+
"rsu"
|
|
853
|
+
]);
|
|
854
|
+
var shareClassTypeEnum = pgEnum("share_class_type", [
|
|
855
|
+
"common",
|
|
856
|
+
"preferred"
|
|
857
|
+
]);
|
|
858
|
+
var equityGrants = pgTable(
|
|
859
|
+
"equity_grants",
|
|
860
|
+
{
|
|
861
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
862
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
863
|
+
headcountId: text("headcount_id").notNull().references(() => headcountPlans.id, { onDelete: "cascade" }),
|
|
864
|
+
grantDate: timestamp("grant_date", { mode: "date" }).notNull(),
|
|
865
|
+
shares: numeric("shares", { precision: 18, scale: 4 }).notNull(),
|
|
866
|
+
strikePrice: numeric("strike_price", { precision: 18, scale: 4 }),
|
|
867
|
+
grantType: equityGrantTypeEnum("grant_type").notNull().default("iso"),
|
|
868
|
+
parameters: jsonb("parameters").notNull().default({}),
|
|
869
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
870
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
871
|
+
},
|
|
872
|
+
(table) => [
|
|
873
|
+
index("equity_grants_company_idx").on(table.companyId),
|
|
874
|
+
index("equity_grants_headcount_idx").on(table.headcountId)
|
|
875
|
+
]
|
|
876
|
+
);
|
|
877
|
+
var bonusTypeEnum = pgEnum("bonus_type", [
|
|
878
|
+
"signing",
|
|
879
|
+
"performance",
|
|
880
|
+
"retention",
|
|
881
|
+
"other"
|
|
882
|
+
]);
|
|
883
|
+
var bonuses = pgTable(
|
|
884
|
+
"bonuses",
|
|
885
|
+
{
|
|
886
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
887
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
888
|
+
headcountId: text("headcount_id").notNull().references(() => headcountPlans.id, { onDelete: "cascade" }),
|
|
889
|
+
payoutMonth: timestamp("payout_month", { mode: "date" }).notNull(),
|
|
890
|
+
amount: numeric("amount", { precision: 12, scale: 2 }).notNull(),
|
|
891
|
+
type: bonusTypeEnum("type").notNull().default("performance"),
|
|
892
|
+
notes: text("notes"),
|
|
893
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
894
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
895
|
+
},
|
|
896
|
+
(table) => [
|
|
897
|
+
index("bonuses_company_idx").on(table.companyId),
|
|
898
|
+
index("bonuses_headcount_month_idx").on(
|
|
899
|
+
table.headcountId,
|
|
900
|
+
table.payoutMonth
|
|
901
|
+
)
|
|
902
|
+
]
|
|
903
|
+
);
|
|
904
|
+
var salaryChanges = pgTable(
|
|
905
|
+
"salary_changes",
|
|
906
|
+
{
|
|
907
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
908
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
909
|
+
headcountId: text("headcount_id").notNull().references(() => headcountPlans.id, { onDelete: "cascade" }),
|
|
910
|
+
effectiveDate: timestamp("effective_date", { mode: "date" }).notNull(),
|
|
911
|
+
newSalary: numeric("new_salary", { precision: 12, scale: 2 }).notNull(),
|
|
912
|
+
reason: text("reason"),
|
|
913
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
914
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
915
|
+
},
|
|
916
|
+
(table) => [
|
|
917
|
+
index("salary_changes_company_idx").on(table.companyId),
|
|
918
|
+
index("salary_changes_headcount_date_idx").on(
|
|
919
|
+
table.headcountId,
|
|
920
|
+
table.effectiveDate
|
|
921
|
+
)
|
|
922
|
+
]
|
|
923
|
+
);
|
|
924
|
+
var revenueStreams = pgTable(
|
|
925
|
+
"revenue_streams",
|
|
926
|
+
{
|
|
927
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
928
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
929
|
+
name: text("name").notNull(),
|
|
930
|
+
type: revenueStreamTypeEnum("type").notNull().default("subscription"),
|
|
931
|
+
parameters: jsonb("parameters").notNull().default({}),
|
|
932
|
+
startDate: timestamp("start_date", { mode: "date" }).notNull().defaultNow(),
|
|
933
|
+
endDate: timestamp("end_date", { mode: "date" }),
|
|
934
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
935
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
936
|
+
},
|
|
937
|
+
(table) => [
|
|
938
|
+
index("revenue_streams_company_idx").on(table.companyId),
|
|
939
|
+
index("revenue_streams_active_idx").on(table.companyId, table.startDate, table.endDate)
|
|
940
|
+
]
|
|
941
|
+
);
|
|
942
|
+
var fundingRounds = pgTable(
|
|
943
|
+
"funding_rounds",
|
|
944
|
+
{
|
|
945
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
946
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
947
|
+
name: text("name").notNull(),
|
|
948
|
+
type: fundingRoundTypeEnum("type").notNull(),
|
|
949
|
+
amount: numeric("amount", { precision: 18, scale: 2 }).notNull(),
|
|
950
|
+
date: timestamp("date", { mode: "date" }).notNull(),
|
|
951
|
+
preMoneyValuation: numeric("pre_money_valuation", {
|
|
952
|
+
precision: 18,
|
|
953
|
+
scale: 2
|
|
954
|
+
}),
|
|
955
|
+
dilutionPercent: numeric("dilution_percent", { precision: 7, scale: 4 }),
|
|
956
|
+
isProjected: boolean("is_projected").notNull().default(false),
|
|
957
|
+
closeDate: timestamp("close_date", { mode: "date" }),
|
|
958
|
+
notes: text("notes"),
|
|
959
|
+
parameters: jsonb("parameters").$type().notNull().default({}),
|
|
960
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
961
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
962
|
+
},
|
|
963
|
+
(table) => [
|
|
964
|
+
index("funding_rounds_company_idx").on(table.companyId)
|
|
965
|
+
]
|
|
966
|
+
);
|
|
967
|
+
var fundingRoundInvestors = pgTable(
|
|
968
|
+
"funding_round_investors",
|
|
969
|
+
{
|
|
970
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
971
|
+
fundingRoundId: text("funding_round_id").notNull().references(() => fundingRounds.id, { onDelete: "cascade" }),
|
|
972
|
+
name: text("name").notNull(),
|
|
973
|
+
email: text("email"),
|
|
974
|
+
amountInvested: numeric("amount_invested", { precision: 18, scale: 2 }).notNull(),
|
|
975
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
976
|
+
},
|
|
977
|
+
(table) => [
|
|
978
|
+
index("funding_round_investors_round_idx").on(table.fundingRoundId)
|
|
979
|
+
]
|
|
980
|
+
);
|
|
981
|
+
var shareClasses = pgTable(
|
|
982
|
+
"share_classes",
|
|
983
|
+
{
|
|
984
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
985
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
986
|
+
name: text("name").notNull(),
|
|
987
|
+
classType: shareClassTypeEnum("class_type").notNull().default("preferred"),
|
|
988
|
+
totalAuthorized: numeric("total_authorized", { precision: 18, scale: 0 }).notNull(),
|
|
989
|
+
totalIssued: numeric("total_issued", { precision: 18, scale: 0 }).notNull().default("0"),
|
|
990
|
+
parValue: numeric("par_value", { precision: 18, scale: 6 }).notNull().default("0.000001"),
|
|
991
|
+
liquidationPreference: numeric("liquidation_preference", {
|
|
992
|
+
precision: 7,
|
|
993
|
+
scale: 4
|
|
994
|
+
}).notNull().default("1.0000"),
|
|
995
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
996
|
+
deletedAt: timestamp("deleted_at", { mode: "date" })
|
|
997
|
+
},
|
|
998
|
+
(table) => [index("share_classes_company_idx").on(table.companyId)]
|
|
999
|
+
);
|
|
1000
|
+
var optionPools = pgTable(
|
|
1001
|
+
"option_pools",
|
|
1002
|
+
{
|
|
1003
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1004
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1005
|
+
name: text("name").notNull(),
|
|
1006
|
+
totalReserved: numeric("total_reserved", { precision: 18, scale: 0 }).notNull(),
|
|
1007
|
+
refreshDate: timestamp("refresh_date", { mode: "date" }),
|
|
1008
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1009
|
+
deletedAt: timestamp("deleted_at", { mode: "date" })
|
|
1010
|
+
},
|
|
1011
|
+
(table) => [index("option_pools_company_idx").on(table.companyId)]
|
|
1012
|
+
);
|
|
1013
|
+
var metrics = pgTable(
|
|
1014
|
+
"metrics",
|
|
1015
|
+
{
|
|
1016
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1017
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1018
|
+
name: text("name").notNull(),
|
|
1019
|
+
slug: text("slug").notNull(),
|
|
1020
|
+
formula: text("formula"),
|
|
1021
|
+
isSystem: boolean("is_system").notNull().default(false),
|
|
1022
|
+
category: metricCategoryEnum("category").notNull().default("financial"),
|
|
1023
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1024
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
1025
|
+
},
|
|
1026
|
+
(table) => [
|
|
1027
|
+
uniqueIndex("metrics_company_slug_idx").on(table.companyId, table.slug)
|
|
1028
|
+
]
|
|
1029
|
+
);
|
|
1030
|
+
var integrations = pgTable(
|
|
1031
|
+
"integrations",
|
|
1032
|
+
{
|
|
1033
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1034
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1035
|
+
type: integrationTypeEnum("type").notNull(),
|
|
1036
|
+
status: integrationStatusEnum("status").notNull().default("active"),
|
|
1037
|
+
lastSyncAt: timestamp("last_sync_at", { mode: "date" }),
|
|
1038
|
+
metadata: jsonb("metadata"),
|
|
1039
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1040
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
1041
|
+
},
|
|
1042
|
+
(table) => [
|
|
1043
|
+
uniqueIndex("integrations_company_type_idx").on(
|
|
1044
|
+
table.companyId,
|
|
1045
|
+
table.type
|
|
1046
|
+
)
|
|
1047
|
+
]
|
|
1048
|
+
);
|
|
1049
|
+
var mcpConnections = pgTable(
|
|
1050
|
+
"mcp_connections",
|
|
1051
|
+
{
|
|
1052
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1053
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1054
|
+
ownerScope: mcpOwnerScopeEnum("owner_scope").notNull().default("company"),
|
|
1055
|
+
/** Required when ownerScope = personal; null for company-shared. */
|
|
1056
|
+
ownerUserId: text("owner_user_id").references(() => users.id, { onDelete: "cascade" }),
|
|
1057
|
+
name: text("name").notNull(),
|
|
1058
|
+
/** Tool-namespace slug derived from name at create time: mcp__<slug>__<tool>. */
|
|
1059
|
+
slug: text("slug").notNull(),
|
|
1060
|
+
transport: mcpTransportEnum("transport").notNull(),
|
|
1061
|
+
/** streamable_http: server URL. stdio: the executable command. */
|
|
1062
|
+
endpoint: text("endpoint").notNull(),
|
|
1063
|
+
/** stdio only: argv after the command. */
|
|
1064
|
+
args: jsonb("args").$type(),
|
|
1065
|
+
/** stdio only: non-secret env passed to the child process. */
|
|
1066
|
+
env: jsonb("env").$type(),
|
|
1067
|
+
authType: mcpAuthTypeEnum("auth_type").notNull().default("none"),
|
|
1068
|
+
status: mcpConnectionStatusEnum("status").notNull().default("pending"),
|
|
1069
|
+
/** Cached handshake result: { protocolVersion?, serverName?, tools: McpToolInfo[] }. */
|
|
1070
|
+
capabilities: jsonb("capabilities").$type(),
|
|
1071
|
+
lastError: text("last_error"),
|
|
1072
|
+
lastConnectedAt: timestamp("last_connected_at", { mode: "date" }),
|
|
1073
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1074
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
1075
|
+
},
|
|
1076
|
+
(table) => [
|
|
1077
|
+
index("mcp_connections_company_idx").on(table.companyId),
|
|
1078
|
+
index("mcp_connections_owner_idx").on(table.companyId, table.ownerScope, table.ownerUserId),
|
|
1079
|
+
uniqueIndex("mcp_connections_company_name_idx").on(table.companyId, table.name),
|
|
1080
|
+
uniqueIndex("mcp_connections_company_slug_idx").on(table.companyId, table.slug),
|
|
1081
|
+
check(
|
|
1082
|
+
"mcp_connections_personal_owner_check",
|
|
1083
|
+
sql`(owner_scope = 'personal') = (owner_user_id IS NOT NULL)`
|
|
1084
|
+
)
|
|
1085
|
+
]
|
|
1086
|
+
);
|
|
1087
|
+
var mcpCredentials = pgTable(
|
|
1088
|
+
"mcp_credentials",
|
|
1089
|
+
{
|
|
1090
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1091
|
+
mcpConnectionId: text("mcp_connection_id").notNull().references(() => mcpConnections.id, { onDelete: "cascade" }),
|
|
1092
|
+
authType: mcpAuthTypeEnum("auth_type").notNull(),
|
|
1093
|
+
/** encryptJson({ accessToken, refreshToken?, expiresAt? } | { token }). NEVER store plaintext. */
|
|
1094
|
+
secret: text("secret"),
|
|
1095
|
+
/** OAuth machinery state (non-secret-ish, but kept server-side only):
|
|
1096
|
+
* { clientInfo?, codeVerifier?, pendingState?, resourceUrl? }. */
|
|
1097
|
+
clientRegistration: jsonb("client_registration").$type(),
|
|
1098
|
+
expiresAt: timestamp("expires_at", { mode: "date" }),
|
|
1099
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1100
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
1101
|
+
},
|
|
1102
|
+
(table) => [uniqueIndex("mcp_credentials_connection_idx").on(table.mcpConnectionId)]
|
|
1103
|
+
);
|
|
1104
|
+
var mcpToolPrefs = pgTable(
|
|
1105
|
+
"mcp_tool_prefs",
|
|
1106
|
+
{
|
|
1107
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1108
|
+
mcpConnectionId: text("mcp_connection_id").notNull().references(() => mcpConnections.id, { onDelete: "cascade" }),
|
|
1109
|
+
toolName: text("tool_name").notNull(),
|
|
1110
|
+
enabled: boolean("enabled").notNull().default(true),
|
|
1111
|
+
permClassOverride: mcpToolPermEnum("perm_class_override"),
|
|
1112
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1113
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
1114
|
+
},
|
|
1115
|
+
(table) => [uniqueIndex("mcp_tool_prefs_connection_tool_idx").on(table.mcpConnectionId, table.toolName)]
|
|
1116
|
+
);
|
|
1117
|
+
var apiTokens = pgTable(
|
|
1118
|
+
"api_tokens",
|
|
1119
|
+
{
|
|
1120
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1121
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
1122
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1123
|
+
name: text("name").notNull(),
|
|
1124
|
+
tokenHash: text("token_hash").notNull(),
|
|
1125
|
+
scopes: jsonb("scopes").$type().notNull(),
|
|
1126
|
+
lastFour: text("last_four").notNull(),
|
|
1127
|
+
expiresAt: timestamp("expires_at", { mode: "date" }),
|
|
1128
|
+
lastUsedAt: timestamp("last_used_at", { mode: "date" }),
|
|
1129
|
+
revokedAt: timestamp("revoked_at", { mode: "date" }),
|
|
1130
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1131
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
1132
|
+
},
|
|
1133
|
+
(table) => [
|
|
1134
|
+
uniqueIndex("api_tokens_hash_idx").on(table.tokenHash),
|
|
1135
|
+
index("api_tokens_user_company_idx").on(table.userId, table.companyId),
|
|
1136
|
+
index("api_tokens_company_idx").on(table.companyId)
|
|
1137
|
+
]
|
|
1138
|
+
);
|
|
1139
|
+
var oauthClients = pgTable("oauth_clients", {
|
|
1140
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1141
|
+
name: text("name").notNull(),
|
|
1142
|
+
redirectUris: jsonb("redirect_uris").$type().notNull(),
|
|
1143
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
1144
|
+
});
|
|
1145
|
+
var oauthAuthCodes = pgTable(
|
|
1146
|
+
"oauth_auth_codes",
|
|
1147
|
+
{
|
|
1148
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1149
|
+
codeHash: text("code_hash").notNull(),
|
|
1150
|
+
clientId: text("client_id").notNull().references(() => oauthClients.id, { onDelete: "cascade" }),
|
|
1151
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
1152
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1153
|
+
scopes: jsonb("scopes").$type().notNull(),
|
|
1154
|
+
codeChallenge: text("code_challenge").notNull(),
|
|
1155
|
+
resource: text("resource").notNull(),
|
|
1156
|
+
redirectUri: text("redirect_uri").notNull(),
|
|
1157
|
+
expiresAt: timestamp("expires_at", { mode: "date" }).notNull(),
|
|
1158
|
+
usedAt: timestamp("used_at", { mode: "date" }),
|
|
1159
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
1160
|
+
},
|
|
1161
|
+
(table) => [uniqueIndex("oauth_auth_codes_hash_idx").on(table.codeHash)]
|
|
1162
|
+
);
|
|
1163
|
+
var oauthTokens = pgTable(
|
|
1164
|
+
"oauth_tokens",
|
|
1165
|
+
{
|
|
1166
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1167
|
+
grantId: text("grant_id").notNull(),
|
|
1168
|
+
clientId: text("client_id").notNull().references(() => oauthClients.id, { onDelete: "cascade" }),
|
|
1169
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
1170
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1171
|
+
scopes: jsonb("scopes").$type().notNull(),
|
|
1172
|
+
accessTokenHash: text("access_token_hash").notNull(),
|
|
1173
|
+
refreshTokenHash: text("refresh_token_hash").notNull(),
|
|
1174
|
+
resource: text("resource").notNull(),
|
|
1175
|
+
accessExpiresAt: timestamp("access_expires_at", { mode: "date" }).notNull(),
|
|
1176
|
+
supersededAt: timestamp("superseded_at", { mode: "date" }),
|
|
1177
|
+
revokedAt: timestamp("revoked_at", { mode: "date" }),
|
|
1178
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1179
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
1180
|
+
},
|
|
1181
|
+
(table) => [
|
|
1182
|
+
uniqueIndex("oauth_tokens_access_hash_idx").on(table.accessTokenHash),
|
|
1183
|
+
uniqueIndex("oauth_tokens_refresh_hash_idx").on(table.refreshTokenHash),
|
|
1184
|
+
index("oauth_tokens_grant_idx").on(table.grantId),
|
|
1185
|
+
index("oauth_tokens_user_company_idx").on(table.userId, table.companyId)
|
|
1186
|
+
]
|
|
1187
|
+
);
|
|
1188
|
+
var aiFeatureFlags = pgTable(
|
|
1189
|
+
"ai_feature_flags",
|
|
1190
|
+
{
|
|
1191
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1192
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1193
|
+
masterEnabled: boolean("master_enabled").notNull().default(true),
|
|
1194
|
+
dataMode: aiDataModeEnum("data_mode").notNull().default("full"),
|
|
1195
|
+
monthlyBudgetCents: integer("monthly_budget_cents").notNull().default(5e3),
|
|
1196
|
+
// $50 default
|
|
1197
|
+
features: jsonb("features").notNull().default({
|
|
1198
|
+
onboarding: true,
|
|
1199
|
+
chat: true,
|
|
1200
|
+
insights: true,
|
|
1201
|
+
uiPersonalization: true,
|
|
1202
|
+
autoCategorization: true,
|
|
1203
|
+
weeklyDigest: true
|
|
1204
|
+
}),
|
|
1205
|
+
// AI write mode — guardrail for AI mutations (full = execute, confirm = ask first, read_only = block)
|
|
1206
|
+
writeMode: aiWriteModeEnum("write_mode").notNull().default("confirm"),
|
|
1207
|
+
// Configurable companion name (default: "Companion")
|
|
1208
|
+
companionName: text("companion_name").notNull().default("Companion"),
|
|
1209
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1210
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
1211
|
+
},
|
|
1212
|
+
(table) => [
|
|
1213
|
+
uniqueIndex("ai_feature_flags_company_idx").on(table.companyId)
|
|
1214
|
+
]
|
|
1215
|
+
);
|
|
1216
|
+
var aiProviders = pgTable(
|
|
1217
|
+
"ai_providers",
|
|
1218
|
+
{
|
|
1219
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1220
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1221
|
+
name: text("name").notNull(),
|
|
1222
|
+
kind: aiProviderKindEnum("kind").notNull(),
|
|
1223
|
+
baseUrl: text("base_url"),
|
|
1224
|
+
apiKeyEncrypted: text("api_key_encrypted"),
|
|
1225
|
+
apiKeyMode: aiApiKeyModeEnum("api_key_mode").notNull().default("user_provided"),
|
|
1226
|
+
headers: jsonb("headers"),
|
|
1227
|
+
dropParams: jsonb("drop_params"),
|
|
1228
|
+
enabled: boolean("enabled").notNull().default(true),
|
|
1229
|
+
isDefault: boolean("is_default").notNull().default(false),
|
|
1230
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1231
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
1232
|
+
},
|
|
1233
|
+
(table) => [index("ai_providers_company_idx").on(table.companyId)]
|
|
1234
|
+
);
|
|
1235
|
+
var aiProviderModels = pgTable(
|
|
1236
|
+
"ai_provider_models",
|
|
1237
|
+
{
|
|
1238
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1239
|
+
providerId: text("provider_id").notNull().references(() => aiProviders.id, { onDelete: "cascade" }),
|
|
1240
|
+
modelId: text("model_id").notNull(),
|
|
1241
|
+
displayName: text("display_name"),
|
|
1242
|
+
contextWindow: integer("context_window"),
|
|
1243
|
+
maxOutputTokens: integer("max_output_tokens"),
|
|
1244
|
+
supportsTools: boolean("supports_tools"),
|
|
1245
|
+
supportsImages: boolean("supports_images"),
|
|
1246
|
+
source: aiProviderModelSourceEnum("source").notNull().default("manual"),
|
|
1247
|
+
enabled: boolean("enabled").notNull().default(true),
|
|
1248
|
+
isDefault: boolean("is_default").notNull().default(false),
|
|
1249
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
1250
|
+
},
|
|
1251
|
+
(table) => [
|
|
1252
|
+
index("ai_provider_models_provider_idx").on(table.providerId),
|
|
1253
|
+
uniqueIndex("ai_provider_models_provider_model_idx").on(table.providerId, table.modelId)
|
|
1254
|
+
]
|
|
1255
|
+
);
|
|
1256
|
+
var aiConversations = pgTable(
|
|
1257
|
+
"ai_conversations",
|
|
1258
|
+
{
|
|
1259
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1260
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1261
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
1262
|
+
title: text("title"),
|
|
1263
|
+
/** Categories granted "for session" in this conversation, e.g. { write: true }. */
|
|
1264
|
+
sessionGrants: jsonb("session_grants").$type().notNull().default({}),
|
|
1265
|
+
/** S3b: tool ids disabled "for this session" in this conversation, e.g. { "tool_id": true }. */
|
|
1266
|
+
sessionDisabledTools: jsonb("session_disabled_tools").$type().notNull().default({}),
|
|
1267
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1268
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
1269
|
+
},
|
|
1270
|
+
(table) => [
|
|
1271
|
+
index("ai_conversations_company_idx").on(table.companyId),
|
|
1272
|
+
index("ai_conversations_company_user_idx").on(table.companyId, table.userId)
|
|
1273
|
+
]
|
|
1274
|
+
);
|
|
1275
|
+
var insightInvalidations = pgTable(
|
|
1276
|
+
"insight_invalidations",
|
|
1277
|
+
{
|
|
1278
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1279
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1280
|
+
insightType: text("insight_type").notNull(),
|
|
1281
|
+
// dashboard | revenue | expense | scenario
|
|
1282
|
+
mutationSource: text("mutation_source").notNull(),
|
|
1283
|
+
// which data changed: expenses, revenue, headcount, funding, scenarios, accounts
|
|
1284
|
+
firstInvalidatedAt: timestamp("first_invalidated_at", { mode: "date" }).defaultNow().notNull(),
|
|
1285
|
+
lastMutationAt: timestamp("last_mutation_at", { mode: "date" }).defaultNow().notNull(),
|
|
1286
|
+
processedAt: timestamp("processed_at", { mode: "date" }),
|
|
1287
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
1288
|
+
},
|
|
1289
|
+
(table) => [
|
|
1290
|
+
uniqueIndex("insight_invalidations_company_type_idx").on(
|
|
1291
|
+
table.companyId,
|
|
1292
|
+
table.insightType
|
|
1293
|
+
),
|
|
1294
|
+
index("insight_invalidations_pending_idx").on(table.processedAt)
|
|
1295
|
+
]
|
|
1296
|
+
);
|
|
1297
|
+
var aiInsightCacheTypeEnum = pgEnum("ai_insight_cache_type", [
|
|
1298
|
+
"dashboard",
|
|
1299
|
+
"revenue",
|
|
1300
|
+
"expense",
|
|
1301
|
+
"scenario",
|
|
1302
|
+
"funding",
|
|
1303
|
+
"team",
|
|
1304
|
+
"reports",
|
|
1305
|
+
"general"
|
|
1306
|
+
]);
|
|
1307
|
+
var aiInsightCache = pgTable(
|
|
1308
|
+
"ai_insight_cache",
|
|
1309
|
+
{
|
|
1310
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1311
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1312
|
+
type: aiInsightCacheTypeEnum("type").notNull(),
|
|
1313
|
+
key: text("key").notNull(),
|
|
1314
|
+
content: jsonb("content").notNull(),
|
|
1315
|
+
expiresAt: timestamp("expires_at", { mode: "date" }),
|
|
1316
|
+
staleAt: timestamp("stale_at", { mode: "date" }),
|
|
1317
|
+
staleReason: text("stale_reason"),
|
|
1318
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1319
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
1320
|
+
},
|
|
1321
|
+
(table) => [
|
|
1322
|
+
index("ai_insight_cache_company_idx").on(table.companyId),
|
|
1323
|
+
uniqueIndex("ai_insight_cache_company_key_idx").on(
|
|
1324
|
+
table.companyId,
|
|
1325
|
+
table.type,
|
|
1326
|
+
table.key
|
|
1327
|
+
)
|
|
1328
|
+
]
|
|
1329
|
+
);
|
|
1330
|
+
var aiMessages = pgTable(
|
|
1331
|
+
"ai_messages",
|
|
1332
|
+
{
|
|
1333
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1334
|
+
conversationId: text("conversation_id").notNull().references(() => aiConversations.id, { onDelete: "cascade" }),
|
|
1335
|
+
role: aiMessageRoleEnum("role").notNull(),
|
|
1336
|
+
content: text("content").notNull(),
|
|
1337
|
+
metadata: jsonb("metadata").$type(),
|
|
1338
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
1339
|
+
},
|
|
1340
|
+
(table) => [
|
|
1341
|
+
index("ai_messages_conversation_created_idx").on(
|
|
1342
|
+
table.conversationId,
|
|
1343
|
+
table.createdAt
|
|
1344
|
+
)
|
|
1345
|
+
]
|
|
1346
|
+
);
|
|
1347
|
+
var aiPendingActionKindEnum = pgEnum("ai_pending_action_kind", [
|
|
1348
|
+
"permission",
|
|
1349
|
+
"input",
|
|
1350
|
+
"plan"
|
|
1351
|
+
]);
|
|
1352
|
+
var aiPendingActions = pgTable(
|
|
1353
|
+
"ai_pending_actions",
|
|
1354
|
+
{
|
|
1355
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1356
|
+
conversationId: text("conversation_id").notNull().references(() => aiConversations.id, { onDelete: "cascade" }),
|
|
1357
|
+
/** Correlates the SSE permission_request/paused event with this row. */
|
|
1358
|
+
pauseId: text("pause_id").notNull(),
|
|
1359
|
+
/** Why the turn paused: a permission decision, or a form-input request. */
|
|
1360
|
+
kind: aiPendingActionKindEnum("kind").notNull().default("permission"),
|
|
1361
|
+
/**
|
|
1362
|
+
* The active scenario the paused turn operates in. Resume MUST run approved
|
|
1363
|
+
* write/delete tools against THIS scenario (loaded scoped to companyId), not
|
|
1364
|
+
* getDefaultScenario — otherwise a pause inside a non-default scenario writes
|
|
1365
|
+
* overrides to the wrong overlay (scenario-safety; spec §5).
|
|
1366
|
+
*/
|
|
1367
|
+
scenarioId: text("scenario_id").notNull(),
|
|
1368
|
+
/**
|
|
1369
|
+
* AI-01: the WRITE target for the paused turn. NULL = base view (the tool
|
|
1370
|
+
* handler writes to base tables). Distinct from `scenarioId`, which stays
|
|
1371
|
+
* non-null for READ context (buildAiContext/getOverrideCount on resume). Only
|
|
1372
|
+
* an explicitly-selected, company-validated scenario is a write target.
|
|
1373
|
+
*/
|
|
1374
|
+
writeScenarioId: text("write_scenario_id"),
|
|
1375
|
+
/** Raw assistant tool-use content blocks for the paused turn. */
|
|
1376
|
+
assistantBlocks: jsonb("assistant_blocks").notNull(),
|
|
1377
|
+
/** tool_result blocks for tools already executed (auto-allowed/denied). */
|
|
1378
|
+
completedResults: jsonb("completed_results").notNull().default([]),
|
|
1379
|
+
/** Actions awaiting a user decision: [{ requestId, tool, category, ... }]. */
|
|
1380
|
+
pending: jsonb("pending").notNull(),
|
|
1381
|
+
/**
|
|
1382
|
+
* Worklog timeline accumulated before this pause (Plan 5 full-run persistence).
|
|
1383
|
+
* Mirrors @burnless/ai's TimelineNode shape; typed `unknown[]` to avoid a
|
|
1384
|
+
* cross-package schema import.
|
|
1385
|
+
*/
|
|
1386
|
+
timeline: jsonb("timeline").$type(),
|
|
1387
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1388
|
+
resolvedAt: timestamp("resolved_at", { mode: "date" })
|
|
1389
|
+
},
|
|
1390
|
+
(table) => [
|
|
1391
|
+
index("ai_pending_actions_conversation_idx").on(table.conversationId),
|
|
1392
|
+
// At most one UNRESOLVED pending batch per conversation.
|
|
1393
|
+
uniqueIndex("ai_pending_actions_active_idx").on(table.conversationId).where(sql`${table.resolvedAt} IS NULL`)
|
|
1394
|
+
]
|
|
1395
|
+
);
|
|
1396
|
+
var dashboardModeEnum = pgEnum("dashboard_mode", [
|
|
1397
|
+
"intelligence",
|
|
1398
|
+
"dynamic",
|
|
1399
|
+
"custom"
|
|
1400
|
+
]);
|
|
1401
|
+
var dashboardPreferences = pgTable(
|
|
1402
|
+
"dashboard_preferences",
|
|
1403
|
+
{
|
|
1404
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1405
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
1406
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1407
|
+
/** Global dashboard display mode */
|
|
1408
|
+
mode: dashboardModeEnum("mode").notNull().default("dynamic"),
|
|
1409
|
+
/** Ordered list of hero card metric slugs (4 cards) */
|
|
1410
|
+
heroCards: jsonb("hero_cards").$type().notNull().default([]),
|
|
1411
|
+
/** Ordered list of secondary metric slugs */
|
|
1412
|
+
secondaryMetrics: jsonb("secondary_metrics").$type().notNull().default([]),
|
|
1413
|
+
/** Per-card mode overrides: { [metricSlug]: "intelligence" | "dynamic" | "custom" } */
|
|
1414
|
+
cardModeOverrides: jsonb("card_mode_overrides").$type().notNull().default({}),
|
|
1415
|
+
/** Per-card scenario overrides: { [metricSlug]: scenarioId } */
|
|
1416
|
+
cardScenarioOverrides: jsonb("card_scenario_overrides").$type().notNull().default({}),
|
|
1417
|
+
/** Custom slug overrides: { "pageId:originalSlug": "newSlug" } — card slot → displayed metric */
|
|
1418
|
+
customSlugOverrides: jsonb("custom_slug_overrides").$type().notNull().default({}),
|
|
1419
|
+
/** Typed slot content overrides: { "pageId:slotId": { type, slug?, ... } } */
|
|
1420
|
+
slotOverrides: jsonb("slot_overrides").$type().notNull().default({}),
|
|
1421
|
+
/** Dashboard widget layout: ordered list of { widgetId, w, h } */
|
|
1422
|
+
layout: jsonb("layout").$type().notNull().default([]),
|
|
1423
|
+
/** User-defined custom metric formulas: [{ id, name, formula, dependsOn }] */
|
|
1424
|
+
customMetrics: jsonb("custom_metrics").$type().notNull().default([]),
|
|
1425
|
+
/** Widget IDs the user has explicitly closed/hidden */
|
|
1426
|
+
closedWidgets: jsonb("closed_widgets").$type().notNull().default([]),
|
|
1427
|
+
/**
|
|
1428
|
+
* Per-page layout overrides: { [pageId]: { order: [...], closedWidgets: [...] } }.
|
|
1429
|
+
* `order` is the screen-independent widget order (current model). `layout`
|
|
1430
|
+
* is the legacy coordinate form, retained for backward-compatible reads of
|
|
1431
|
+
* rows written before the fluid-flow migration.
|
|
1432
|
+
*/
|
|
1433
|
+
pageLayouts: jsonb("page_layouts").$type().notNull().default({}),
|
|
1434
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1435
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
1436
|
+
},
|
|
1437
|
+
(table) => [
|
|
1438
|
+
uniqueIndex("dashboard_prefs_user_company_idx").on(table.userId, table.companyId)
|
|
1439
|
+
]
|
|
1440
|
+
);
|
|
1441
|
+
var usersRelations = relations(users, ({ many }) => ({
|
|
1442
|
+
companyMemberships: many(companyMembers),
|
|
1443
|
+
ownedCompanies: many(companies),
|
|
1444
|
+
dashboardPreferences: many(dashboardPreferences)
|
|
1445
|
+
}));
|
|
1446
|
+
var companiesRelations = relations(companies, ({ one, many }) => ({
|
|
1447
|
+
owner: one(users, {
|
|
1448
|
+
fields: [companies.ownerId],
|
|
1449
|
+
references: [users.id]
|
|
1450
|
+
}),
|
|
1451
|
+
members: many(companyMembers),
|
|
1452
|
+
financialAccounts: many(financialAccounts),
|
|
1453
|
+
transactions: many(transactions),
|
|
1454
|
+
scenarios: many(scenarios),
|
|
1455
|
+
forecastLines: many(forecastLines),
|
|
1456
|
+
headcountPlans: many(headcountPlans),
|
|
1457
|
+
revenueStreams: many(revenueStreams),
|
|
1458
|
+
departments: many(departments),
|
|
1459
|
+
fundingRounds: many(fundingRounds),
|
|
1460
|
+
metrics: many(metrics),
|
|
1461
|
+
integrations: many(integrations),
|
|
1462
|
+
importBatches: many(importBatches),
|
|
1463
|
+
aiFeatureFlags: many(aiFeatureFlags),
|
|
1464
|
+
aiProviders: many(aiProviders),
|
|
1465
|
+
aiConversations: many(aiConversations),
|
|
1466
|
+
aiInsightCache: many(aiInsightCache),
|
|
1467
|
+
weeklyDigests: many(weeklyDigests),
|
|
1468
|
+
financialAuditLogs: many(financialAuditLogs),
|
|
1469
|
+
dashboardPreferences: many(dashboardPreferences),
|
|
1470
|
+
mcpConnections: many(mcpConnections)
|
|
1471
|
+
}));
|
|
1472
|
+
var dashboardPreferencesRelations = relations(dashboardPreferences, ({ one }) => ({
|
|
1473
|
+
user: one(users, {
|
|
1474
|
+
fields: [dashboardPreferences.userId],
|
|
1475
|
+
references: [users.id]
|
|
1476
|
+
}),
|
|
1477
|
+
company: one(companies, {
|
|
1478
|
+
fields: [dashboardPreferences.companyId],
|
|
1479
|
+
references: [companies.id]
|
|
1480
|
+
})
|
|
1481
|
+
}));
|
|
1482
|
+
var companyMembersRelations = relations(companyMembers, ({ one }) => ({
|
|
1483
|
+
company: one(companies, {
|
|
1484
|
+
fields: [companyMembers.companyId],
|
|
1485
|
+
references: [companies.id]
|
|
1486
|
+
}),
|
|
1487
|
+
user: one(users, {
|
|
1488
|
+
fields: [companyMembers.userId],
|
|
1489
|
+
references: [users.id]
|
|
1490
|
+
})
|
|
1491
|
+
}));
|
|
1492
|
+
var financialAccountsRelations = relations(
|
|
1493
|
+
financialAccounts,
|
|
1494
|
+
({ one, many }) => ({
|
|
1495
|
+
company: one(companies, {
|
|
1496
|
+
fields: [financialAccounts.companyId],
|
|
1497
|
+
references: [companies.id]
|
|
1498
|
+
}),
|
|
1499
|
+
parent: one(financialAccounts, {
|
|
1500
|
+
fields: [financialAccounts.parentId],
|
|
1501
|
+
references: [financialAccounts.id]
|
|
1502
|
+
}),
|
|
1503
|
+
transactions: many(transactions),
|
|
1504
|
+
forecastLines: many(forecastLines)
|
|
1505
|
+
})
|
|
1506
|
+
);
|
|
1507
|
+
var transactionsRelations = relations(transactions, ({ one }) => ({
|
|
1508
|
+
company: one(companies, {
|
|
1509
|
+
fields: [transactions.companyId],
|
|
1510
|
+
references: [companies.id]
|
|
1511
|
+
}),
|
|
1512
|
+
account: one(financialAccounts, {
|
|
1513
|
+
fields: [transactions.accountId],
|
|
1514
|
+
references: [financialAccounts.id]
|
|
1515
|
+
}),
|
|
1516
|
+
importBatch: one(importBatches, {
|
|
1517
|
+
fields: [transactions.importBatchId],
|
|
1518
|
+
references: [importBatches.id]
|
|
1519
|
+
})
|
|
1520
|
+
}));
|
|
1521
|
+
var importBatchesRelations = relations(
|
|
1522
|
+
importBatches,
|
|
1523
|
+
({ one, many }) => ({
|
|
1524
|
+
company: one(companies, {
|
|
1525
|
+
fields: [importBatches.companyId],
|
|
1526
|
+
references: [companies.id]
|
|
1527
|
+
}),
|
|
1528
|
+
account: one(financialAccounts, {
|
|
1529
|
+
fields: [importBatches.accountId],
|
|
1530
|
+
references: [financialAccounts.id]
|
|
1531
|
+
}),
|
|
1532
|
+
transactions: many(transactions)
|
|
1533
|
+
})
|
|
1534
|
+
);
|
|
1535
|
+
var scenariosRelations = relations(scenarios, ({ one, many }) => ({
|
|
1536
|
+
company: one(companies, {
|
|
1537
|
+
fields: [scenarios.companyId],
|
|
1538
|
+
references: [companies.id]
|
|
1539
|
+
}),
|
|
1540
|
+
overrides: many(scenarioOverrides)
|
|
1541
|
+
}));
|
|
1542
|
+
var scenarioOverridesRelations = relations(scenarioOverrides, ({ one }) => ({
|
|
1543
|
+
scenario: one(scenarios, {
|
|
1544
|
+
fields: [scenarioOverrides.scenarioId],
|
|
1545
|
+
references: [scenarios.id]
|
|
1546
|
+
})
|
|
1547
|
+
}));
|
|
1548
|
+
var forecastLinesRelations = relations(
|
|
1549
|
+
forecastLines,
|
|
1550
|
+
({ one, many }) => ({
|
|
1551
|
+
company: one(companies, {
|
|
1552
|
+
fields: [forecastLines.companyId],
|
|
1553
|
+
references: [companies.id]
|
|
1554
|
+
}),
|
|
1555
|
+
account: one(financialAccounts, {
|
|
1556
|
+
fields: [forecastLines.accountId],
|
|
1557
|
+
references: [financialAccounts.id]
|
|
1558
|
+
}),
|
|
1559
|
+
values: many(forecastValues)
|
|
1560
|
+
})
|
|
1561
|
+
);
|
|
1562
|
+
var forecastValuesRelations = relations(forecastValues, ({ one }) => ({
|
|
1563
|
+
forecastLine: one(forecastLines, {
|
|
1564
|
+
fields: [forecastValues.forecastLineId],
|
|
1565
|
+
references: [forecastLines.id]
|
|
1566
|
+
})
|
|
1567
|
+
}));
|
|
1568
|
+
var departmentsRelations = relations(departments, ({ one, many }) => ({
|
|
1569
|
+
company: one(companies, {
|
|
1570
|
+
fields: [departments.companyId],
|
|
1571
|
+
references: [companies.id]
|
|
1572
|
+
}),
|
|
1573
|
+
headcountPlans: many(headcountPlans)
|
|
1574
|
+
}));
|
|
1575
|
+
var headcountPlansRelations = relations(headcountPlans, ({ one, many }) => ({
|
|
1576
|
+
company: one(companies, {
|
|
1577
|
+
fields: [headcountPlans.companyId],
|
|
1578
|
+
references: [companies.id]
|
|
1579
|
+
}),
|
|
1580
|
+
department: one(departments, {
|
|
1581
|
+
fields: [headcountPlans.departmentId],
|
|
1582
|
+
references: [departments.id]
|
|
1583
|
+
}),
|
|
1584
|
+
salaryChanges: many(salaryChanges),
|
|
1585
|
+
bonuses: many(bonuses),
|
|
1586
|
+
equityGrants: many(equityGrants)
|
|
1587
|
+
}));
|
|
1588
|
+
var salaryChangesRelations = relations(salaryChanges, ({ one }) => ({
|
|
1589
|
+
company: one(companies, {
|
|
1590
|
+
fields: [salaryChanges.companyId],
|
|
1591
|
+
references: [companies.id]
|
|
1592
|
+
}),
|
|
1593
|
+
headcount: one(headcountPlans, {
|
|
1594
|
+
fields: [salaryChanges.headcountId],
|
|
1595
|
+
references: [headcountPlans.id]
|
|
1596
|
+
})
|
|
1597
|
+
}));
|
|
1598
|
+
var bonusesRelations = relations(bonuses, ({ one }) => ({
|
|
1599
|
+
company: one(companies, {
|
|
1600
|
+
fields: [bonuses.companyId],
|
|
1601
|
+
references: [companies.id]
|
|
1602
|
+
}),
|
|
1603
|
+
headcount: one(headcountPlans, {
|
|
1604
|
+
fields: [bonuses.headcountId],
|
|
1605
|
+
references: [headcountPlans.id]
|
|
1606
|
+
})
|
|
1607
|
+
}));
|
|
1608
|
+
var equityGrantsRelations = relations(equityGrants, ({ one }) => ({
|
|
1609
|
+
company: one(companies, {
|
|
1610
|
+
fields: [equityGrants.companyId],
|
|
1611
|
+
references: [companies.id]
|
|
1612
|
+
}),
|
|
1613
|
+
headcount: one(headcountPlans, {
|
|
1614
|
+
fields: [equityGrants.headcountId],
|
|
1615
|
+
references: [headcountPlans.id]
|
|
1616
|
+
})
|
|
1617
|
+
}));
|
|
1618
|
+
var revenueStreamsRelations = relations(revenueStreams, ({ one }) => ({
|
|
1619
|
+
company: one(companies, {
|
|
1620
|
+
fields: [revenueStreams.companyId],
|
|
1621
|
+
references: [companies.id]
|
|
1622
|
+
})
|
|
1623
|
+
}));
|
|
1624
|
+
var fundingRoundsRelations = relations(fundingRounds, ({ one, many }) => ({
|
|
1625
|
+
company: one(companies, { fields: [fundingRounds.companyId], references: [companies.id] }),
|
|
1626
|
+
investors: many(fundingRoundInvestors)
|
|
1627
|
+
}));
|
|
1628
|
+
var fundingRoundInvestorsRelations = relations(fundingRoundInvestors, ({ one }) => ({
|
|
1629
|
+
round: one(fundingRounds, {
|
|
1630
|
+
fields: [fundingRoundInvestors.fundingRoundId],
|
|
1631
|
+
references: [fundingRounds.id]
|
|
1632
|
+
})
|
|
1633
|
+
}));
|
|
1634
|
+
var shareClassesRelations = relations(shareClasses, ({ one }) => ({
|
|
1635
|
+
company: one(companies, { fields: [shareClasses.companyId], references: [companies.id] })
|
|
1636
|
+
}));
|
|
1637
|
+
var optionPoolsRelations = relations(optionPools, ({ one }) => ({
|
|
1638
|
+
company: one(companies, { fields: [optionPools.companyId], references: [companies.id] })
|
|
1639
|
+
}));
|
|
1640
|
+
var metricsRelations = relations(metrics, ({ one }) => ({
|
|
1641
|
+
company: one(companies, {
|
|
1642
|
+
fields: [metrics.companyId],
|
|
1643
|
+
references: [companies.id]
|
|
1644
|
+
})
|
|
1645
|
+
}));
|
|
1646
|
+
var integrationsRelations = relations(integrations, ({ one }) => ({
|
|
1647
|
+
company: one(companies, {
|
|
1648
|
+
fields: [integrations.companyId],
|
|
1649
|
+
references: [companies.id]
|
|
1650
|
+
})
|
|
1651
|
+
}));
|
|
1652
|
+
var aiFeatureFlagsRelations = relations(aiFeatureFlags, ({ one }) => ({
|
|
1653
|
+
company: one(companies, {
|
|
1654
|
+
fields: [aiFeatureFlags.companyId],
|
|
1655
|
+
references: [companies.id]
|
|
1656
|
+
})
|
|
1657
|
+
}));
|
|
1658
|
+
var aiProvidersRelations = relations(aiProviders, ({ one, many }) => ({
|
|
1659
|
+
company: one(companies, { fields: [aiProviders.companyId], references: [companies.id] }),
|
|
1660
|
+
models: many(aiProviderModels)
|
|
1661
|
+
}));
|
|
1662
|
+
var aiProviderModelsRelations = relations(aiProviderModels, ({ one }) => ({
|
|
1663
|
+
provider: one(aiProviders, { fields: [aiProviderModels.providerId], references: [aiProviders.id] })
|
|
1664
|
+
}));
|
|
1665
|
+
var aiConversationsRelations = relations(
|
|
1666
|
+
aiConversations,
|
|
1667
|
+
({ one, many }) => ({
|
|
1668
|
+
company: one(companies, {
|
|
1669
|
+
fields: [aiConversations.companyId],
|
|
1670
|
+
references: [companies.id]
|
|
1671
|
+
}),
|
|
1672
|
+
user: one(users, {
|
|
1673
|
+
fields: [aiConversations.userId],
|
|
1674
|
+
references: [users.id]
|
|
1675
|
+
}),
|
|
1676
|
+
messages: many(aiMessages)
|
|
1677
|
+
})
|
|
1678
|
+
);
|
|
1679
|
+
var aiInsightCacheRelations = relations(aiInsightCache, ({ one }) => ({
|
|
1680
|
+
company: one(companies, {
|
|
1681
|
+
fields: [aiInsightCache.companyId],
|
|
1682
|
+
references: [companies.id]
|
|
1683
|
+
})
|
|
1684
|
+
}));
|
|
1685
|
+
var aiMessagesRelations = relations(aiMessages, ({ one }) => ({
|
|
1686
|
+
conversation: one(aiConversations, {
|
|
1687
|
+
fields: [aiMessages.conversationId],
|
|
1688
|
+
references: [aiConversations.id]
|
|
1689
|
+
})
|
|
1690
|
+
}));
|
|
1691
|
+
var weeklyDigests = pgTable(
|
|
1692
|
+
"weekly_digests",
|
|
1693
|
+
{
|
|
1694
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1695
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1696
|
+
weekStart: timestamp("week_start", { mode: "date" }).notNull(),
|
|
1697
|
+
metrics: jsonb("metrics").notNull(),
|
|
1698
|
+
narrative: text("narrative"),
|
|
1699
|
+
deterministicSummary: text("deterministic_summary").notNull(),
|
|
1700
|
+
emailSentAt: timestamp("email_sent_at", { mode: "date" }),
|
|
1701
|
+
dismissedAt: timestamp("dismissed_at", { mode: "date" }),
|
|
1702
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
1703
|
+
},
|
|
1704
|
+
(table) => [
|
|
1705
|
+
index("weekly_digests_company_idx").on(table.companyId),
|
|
1706
|
+
uniqueIndex("weekly_digests_company_week_idx").on(
|
|
1707
|
+
table.companyId,
|
|
1708
|
+
table.weekStart
|
|
1709
|
+
)
|
|
1710
|
+
]
|
|
1711
|
+
);
|
|
1712
|
+
var weeklyDigestsRelations = relations(weeklyDigests, ({ one }) => ({
|
|
1713
|
+
company: one(companies, {
|
|
1714
|
+
fields: [weeklyDigests.companyId],
|
|
1715
|
+
references: [companies.id]
|
|
1716
|
+
})
|
|
1717
|
+
}));
|
|
1718
|
+
var consentPurposeEnum = pgEnum("consent_purpose", [
|
|
1719
|
+
"data_processing",
|
|
1720
|
+
"ai_features",
|
|
1721
|
+
"marketing",
|
|
1722
|
+
"analytics"
|
|
1723
|
+
]);
|
|
1724
|
+
var privacyConsents = pgTable(
|
|
1725
|
+
"privacy_consents",
|
|
1726
|
+
{
|
|
1727
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1728
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
1729
|
+
purpose: consentPurposeEnum("purpose").notNull(),
|
|
1730
|
+
granted: boolean("granted").notNull(),
|
|
1731
|
+
ipAddress: text("ip_address"),
|
|
1732
|
+
userAgent: text("user_agent"),
|
|
1733
|
+
grantedAt: timestamp("granted_at", { mode: "date" }).defaultNow().notNull(),
|
|
1734
|
+
revokedAt: timestamp("revoked_at", { mode: "date" }),
|
|
1735
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
1736
|
+
},
|
|
1737
|
+
(table) => [
|
|
1738
|
+
index("privacy_consents_user_idx").on(table.userId),
|
|
1739
|
+
index("privacy_consents_user_purpose_idx").on(table.userId, table.purpose)
|
|
1740
|
+
]
|
|
1741
|
+
);
|
|
1742
|
+
var privacyConsentsRelations = relations(
|
|
1743
|
+
privacyConsents,
|
|
1744
|
+
({ one }) => ({
|
|
1745
|
+
user: one(users, {
|
|
1746
|
+
fields: [privacyConsents.userId],
|
|
1747
|
+
references: [users.id]
|
|
1748
|
+
})
|
|
1749
|
+
})
|
|
1750
|
+
);
|
|
1751
|
+
var merchantCategoryMappings = pgTable(
|
|
1752
|
+
"merchant_category_mappings",
|
|
1753
|
+
{
|
|
1754
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1755
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1756
|
+
merchantPattern: text("merchant_pattern").notNull(),
|
|
1757
|
+
accountId: text("account_id").notNull().references(() => financialAccounts.id, { onDelete: "cascade" }),
|
|
1758
|
+
category: accountCategoryEnum("category").notNull(),
|
|
1759
|
+
subcategory: text("subcategory").notNull(),
|
|
1760
|
+
source: text("source").notNull().default("user_override"),
|
|
1761
|
+
overrideCount: integer("override_count").notNull().default(1),
|
|
1762
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1763
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
1764
|
+
},
|
|
1765
|
+
(table) => [
|
|
1766
|
+
index("merchant_mappings_company_idx").on(table.companyId),
|
|
1767
|
+
index("merchant_mappings_pattern_idx").on(table.merchantPattern),
|
|
1768
|
+
index("merchant_mappings_account_idx").on(table.accountId),
|
|
1769
|
+
uniqueIndex("merchant_mappings_company_pattern_idx").on(
|
|
1770
|
+
table.companyId,
|
|
1771
|
+
table.merchantPattern
|
|
1772
|
+
)
|
|
1773
|
+
]
|
|
1774
|
+
);
|
|
1775
|
+
var merchantCategoryMappingsRelations = relations(
|
|
1776
|
+
merchantCategoryMappings,
|
|
1777
|
+
({ one }) => ({
|
|
1778
|
+
company: one(companies, {
|
|
1779
|
+
fields: [merchantCategoryMappings.companyId],
|
|
1780
|
+
references: [companies.id]
|
|
1781
|
+
}),
|
|
1782
|
+
account: one(financialAccounts, {
|
|
1783
|
+
fields: [merchantCategoryMappings.accountId],
|
|
1784
|
+
references: [financialAccounts.id]
|
|
1785
|
+
})
|
|
1786
|
+
})
|
|
1787
|
+
);
|
|
1788
|
+
var auditActionEnum = pgEnum("audit_action", [
|
|
1789
|
+
"create",
|
|
1790
|
+
"update",
|
|
1791
|
+
"delete",
|
|
1792
|
+
"import",
|
|
1793
|
+
"rollback"
|
|
1794
|
+
]);
|
|
1795
|
+
var auditEntityTypeEnum = pgEnum("audit_entity_type", [
|
|
1796
|
+
"transaction",
|
|
1797
|
+
"financial_account",
|
|
1798
|
+
"scenario",
|
|
1799
|
+
"forecast_line",
|
|
1800
|
+
"forecast_value",
|
|
1801
|
+
"headcount_plan",
|
|
1802
|
+
"revenue_stream",
|
|
1803
|
+
"funding_round",
|
|
1804
|
+
"import_batch",
|
|
1805
|
+
"department",
|
|
1806
|
+
"metric",
|
|
1807
|
+
"salary_change",
|
|
1808
|
+
"bonus",
|
|
1809
|
+
"equity_grant",
|
|
1810
|
+
"funding_round_investor",
|
|
1811
|
+
"share_class",
|
|
1812
|
+
"option_pool"
|
|
1813
|
+
]);
|
|
1814
|
+
var financialAuditLogs = pgTable(
|
|
1815
|
+
"financial_audit_logs",
|
|
1816
|
+
{
|
|
1817
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1818
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1819
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
1820
|
+
entityType: auditEntityTypeEnum("entity_type").notNull(),
|
|
1821
|
+
entityId: text("entity_id").notNull(),
|
|
1822
|
+
action: auditActionEnum("action").notNull(),
|
|
1823
|
+
changes: jsonb("changes"),
|
|
1824
|
+
// { before?: Record, after?: Record } — null for creates/deletes when full snapshot not needed
|
|
1825
|
+
metadata: jsonb("metadata"),
|
|
1826
|
+
// extra context: IP, user agent, import batch ID, etc.
|
|
1827
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
1828
|
+
},
|
|
1829
|
+
(table) => [
|
|
1830
|
+
index("financial_audit_company_idx").on(table.companyId),
|
|
1831
|
+
index("financial_audit_entity_idx").on(table.entityType, table.entityId),
|
|
1832
|
+
index("financial_audit_user_idx").on(table.companyId, table.userId),
|
|
1833
|
+
index("financial_audit_created_idx").on(table.companyId, table.createdAt)
|
|
1834
|
+
]
|
|
1835
|
+
);
|
|
1836
|
+
var financialAuditLogsRelations = relations(
|
|
1837
|
+
financialAuditLogs,
|
|
1838
|
+
({ one }) => ({
|
|
1839
|
+
company: one(companies, {
|
|
1840
|
+
fields: [financialAuditLogs.companyId],
|
|
1841
|
+
references: [companies.id]
|
|
1842
|
+
}),
|
|
1843
|
+
user: one(users, {
|
|
1844
|
+
fields: [financialAuditLogs.userId],
|
|
1845
|
+
references: [users.id]
|
|
1846
|
+
})
|
|
1847
|
+
})
|
|
1848
|
+
);
|
|
1849
|
+
var scheduledJobActionKindEnum = pgEnum("scheduled_job_action_kind", ["write", "notify"]);
|
|
1850
|
+
var scheduledJobStatusEnum = pgEnum("scheduled_job_status", [
|
|
1851
|
+
"active",
|
|
1852
|
+
"disabled",
|
|
1853
|
+
"auto_disabled",
|
|
1854
|
+
"error"
|
|
1855
|
+
]);
|
|
1856
|
+
var scheduledJobNotifyPolicyEnum = pgEnum("scheduled_job_notify_policy", [
|
|
1857
|
+
"smart",
|
|
1858
|
+
"failures",
|
|
1859
|
+
"every",
|
|
1860
|
+
"off"
|
|
1861
|
+
]);
|
|
1862
|
+
var scheduledJobRunStatusEnum = pgEnum("scheduled_job_run_status", [
|
|
1863
|
+
"running",
|
|
1864
|
+
"success",
|
|
1865
|
+
"failed",
|
|
1866
|
+
"missed"
|
|
1867
|
+
]);
|
|
1868
|
+
var scheduledJobRunTriggerEnum = pgEnum("scheduled_job_run_trigger", [
|
|
1869
|
+
"schedule",
|
|
1870
|
+
"manual",
|
|
1871
|
+
"dry_run"
|
|
1872
|
+
]);
|
|
1873
|
+
var scheduledJobs = pgTable(
|
|
1874
|
+
"scheduled_jobs",
|
|
1875
|
+
{
|
|
1876
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1877
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1878
|
+
createdByUserId: text("created_by_user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
1879
|
+
name: text("name").notNull(),
|
|
1880
|
+
prompt: text("prompt").notNull(),
|
|
1881
|
+
actionKind: scheduledJobActionKindEnum("action_kind").notNull(),
|
|
1882
|
+
/** Frozen tool-name allowlist (financial tool names + `mcp__*` names). */
|
|
1883
|
+
allowedTools: jsonb("allowed_tools").$type().notNull().default([]),
|
|
1884
|
+
/** MCP connection ids whose creds the run resolves. */
|
|
1885
|
+
boundConnectionIds: jsonb("bound_connection_ids").$type().notNull().default([]),
|
|
1886
|
+
/** 5-field UTC cron expression. */
|
|
1887
|
+
schedule: text("schedule").notNull(),
|
|
1888
|
+
timezone: text("timezone").notNull().default("UTC"),
|
|
1889
|
+
enabled: boolean("enabled").notNull().default(true),
|
|
1890
|
+
status: scheduledJobStatusEnum("status").notNull().default("active"),
|
|
1891
|
+
notifyPolicy: scheduledJobNotifyPolicyEnum("notify_policy").notNull().default("smart"),
|
|
1892
|
+
consecutiveFailures: integer("consecutive_failures").notNull().default(0),
|
|
1893
|
+
lastRunAt: timestamp("last_run_at", { mode: "date" }),
|
|
1894
|
+
nextRunAt: timestamp("next_run_at", { mode: "date" }),
|
|
1895
|
+
/** Idempotency context handed to the agent on the next run (last-run cursor/summary). */
|
|
1896
|
+
lastRunCursor: jsonb("last_run_cursor").$type(),
|
|
1897
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1898
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date()),
|
|
1899
|
+
deletedAt: timestamp("deleted_at", { mode: "date" })
|
|
1900
|
+
},
|
|
1901
|
+
(table) => [
|
|
1902
|
+
index("scheduled_jobs_company_idx").on(table.companyId),
|
|
1903
|
+
index("scheduled_jobs_due_idx").on(table.enabled, table.nextRunAt)
|
|
1904
|
+
]
|
|
1905
|
+
);
|
|
1906
|
+
var scheduledJobRuns = pgTable(
|
|
1907
|
+
"scheduled_job_runs",
|
|
1908
|
+
{
|
|
1909
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1910
|
+
scheduledJobId: text("scheduled_job_id").notNull().references(() => scheduledJobs.id, { onDelete: "cascade" }),
|
|
1911
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1912
|
+
status: scheduledJobRunStatusEnum("status").notNull(),
|
|
1913
|
+
trigger: scheduledJobRunTriggerEnum("trigger").notNull(),
|
|
1914
|
+
startedAt: timestamp("started_at", { mode: "date" }).defaultNow().notNull(),
|
|
1915
|
+
finishedAt: timestamp("finished_at", { mode: "date" }),
|
|
1916
|
+
durationMs: integer("duration_ms"),
|
|
1917
|
+
tokensUsed: integer("tokens_used"),
|
|
1918
|
+
summary: text("summary"),
|
|
1919
|
+
output: jsonb("output").$type(),
|
|
1920
|
+
error: text("error")
|
|
1921
|
+
},
|
|
1922
|
+
(table) => [
|
|
1923
|
+
index("scheduled_job_runs_job_idx").on(table.scheduledJobId, table.startedAt),
|
|
1924
|
+
index("scheduled_job_runs_company_idx").on(table.companyId)
|
|
1925
|
+
]
|
|
1926
|
+
);
|
|
1927
|
+
var aiToolAuditLogStatusEnum = pgEnum("ai_tool_audit_log_status", [
|
|
1928
|
+
"success",
|
|
1929
|
+
"error",
|
|
1930
|
+
"validation_error",
|
|
1931
|
+
"pending_apply"
|
|
1932
|
+
]);
|
|
1933
|
+
var aiToolAuditLogs = pgTable(
|
|
1934
|
+
"ai_tool_audit_logs",
|
|
1935
|
+
{
|
|
1936
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1937
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
1938
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
1939
|
+
conversationId: text("conversation_id").references(() => aiConversations.id, { onDelete: "set null" }),
|
|
1940
|
+
/** Set when the tool was an external MCP tool (spec §3.3 audit reuse). */
|
|
1941
|
+
mcpConnectionId: text("mcp_connection_id").references(() => mcpConnections.id, { onDelete: "set null" }),
|
|
1942
|
+
/** When set, links every tool call to its scheduled-job run (S3a Plan 4). */
|
|
1943
|
+
scheduledJobRunId: text("scheduled_job_run_id").references(() => scheduledJobRuns.id, { onDelete: "set null" }),
|
|
1944
|
+
toolName: text("tool_name").notNull(),
|
|
1945
|
+
input: jsonb("input").notNull(),
|
|
1946
|
+
status: aiToolAuditLogStatusEnum("status").notNull(),
|
|
1947
|
+
permissionDecision: aiToolPermissionDecisionEnum("permission_decision"),
|
|
1948
|
+
result: jsonb("result"),
|
|
1949
|
+
durationMs: integer("duration_ms"),
|
|
1950
|
+
/** Audit attribution (expose spec B5): where the call came from.
|
|
1951
|
+
* Existing chat writers rely on the default — do not backfill. */
|
|
1952
|
+
source: text("source").notNull().default("chat"),
|
|
1953
|
+
/** "pat" | "oauth" — set only for source='mcp_server' rows. */
|
|
1954
|
+
credentialType: text("credential_type"),
|
|
1955
|
+
/** apiTokens.id (PAT) or oauthTokens.grantId (OAuth grant family). */
|
|
1956
|
+
credentialId: text("credential_id"),
|
|
1957
|
+
/** MCP initialize handshake identity, e.g. { name: "burnless-cli", version: "0.1.0" }. */
|
|
1958
|
+
clientInfo: jsonb("client_info").$type(),
|
|
1959
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
1960
|
+
},
|
|
1961
|
+
(table) => [
|
|
1962
|
+
index("ai_tool_audit_company_idx").on(table.companyId),
|
|
1963
|
+
index("ai_tool_audit_user_idx").on(table.companyId, table.userId),
|
|
1964
|
+
index("ai_tool_audit_created_idx").on(table.companyId, table.createdAt),
|
|
1965
|
+
index("ai_tool_audit_tool_idx").on(table.companyId, table.toolName),
|
|
1966
|
+
index("ai_tool_audit_conversation_idx").on(table.conversationId),
|
|
1967
|
+
index("ai_tool_audit_mcp_connection_idx").on(table.mcpConnectionId),
|
|
1968
|
+
index("ai_tool_audit_scheduled_job_run_idx").on(table.scheduledJobRunId)
|
|
1969
|
+
]
|
|
1970
|
+
);
|
|
1971
|
+
var inviteCodeTypeEnum = pgEnum("invite_code_type", [
|
|
1972
|
+
"single_use",
|
|
1973
|
+
"multi_use"
|
|
1974
|
+
]);
|
|
1975
|
+
var inviteCodes = pgTable(
|
|
1976
|
+
"invite_codes",
|
|
1977
|
+
{
|
|
1978
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
1979
|
+
code: text("code").notNull(),
|
|
1980
|
+
type: inviteCodeTypeEnum("type").notNull().default("single_use"),
|
|
1981
|
+
maxRedemptions: integer("max_redemptions").notNull().default(1),
|
|
1982
|
+
currentRedemptions: integer("current_redemptions").notNull().default(0),
|
|
1983
|
+
expiresAt: timestamp("expires_at", { mode: "date" }),
|
|
1984
|
+
freePlatformDays: integer("free_platform_days").notNull().default(30),
|
|
1985
|
+
aiCreditsCents: integer("ai_credits_cents").notNull().default(5e3),
|
|
1986
|
+
// $50 default
|
|
1987
|
+
isActive: boolean("is_active").notNull().default(true),
|
|
1988
|
+
createdBy: text("created_by").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
1989
|
+
note: text("note"),
|
|
1990
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
1991
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
1992
|
+
},
|
|
1993
|
+
(table) => [
|
|
1994
|
+
uniqueIndex("invite_codes_code_idx").on(table.code),
|
|
1995
|
+
index("invite_codes_created_by_idx").on(table.createdBy),
|
|
1996
|
+
index("invite_codes_active_idx").on(table.isActive)
|
|
1997
|
+
]
|
|
1998
|
+
);
|
|
1999
|
+
var inviteCodeRedemptions = pgTable(
|
|
2000
|
+
"invite_code_redemptions",
|
|
2001
|
+
{
|
|
2002
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
2003
|
+
inviteCodeId: text("invite_code_id").notNull().references(() => inviteCodes.id, { onDelete: "cascade" }),
|
|
2004
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
2005
|
+
redeemedAt: timestamp("redeemed_at", { mode: "date" }).defaultNow().notNull()
|
|
2006
|
+
},
|
|
2007
|
+
(table) => [
|
|
2008
|
+
index("invite_redemptions_code_idx").on(table.inviteCodeId),
|
|
2009
|
+
uniqueIndex("invite_redemptions_user_code_idx").on(
|
|
2010
|
+
table.inviteCodeId,
|
|
2011
|
+
table.userId
|
|
2012
|
+
)
|
|
2013
|
+
]
|
|
2014
|
+
);
|
|
2015
|
+
var inviteCodesRelations = relations(inviteCodes, ({ one, many }) => ({
|
|
2016
|
+
creator: one(users, {
|
|
2017
|
+
fields: [inviteCodes.createdBy],
|
|
2018
|
+
references: [users.id]
|
|
2019
|
+
}),
|
|
2020
|
+
redemptions: many(inviteCodeRedemptions)
|
|
2021
|
+
}));
|
|
2022
|
+
var inviteCodeRedemptionsRelations = relations(
|
|
2023
|
+
inviteCodeRedemptions,
|
|
2024
|
+
({ one }) => ({
|
|
2025
|
+
inviteCode: one(inviteCodes, {
|
|
2026
|
+
fields: [inviteCodeRedemptions.inviteCodeId],
|
|
2027
|
+
references: [inviteCodes.id]
|
|
2028
|
+
}),
|
|
2029
|
+
user: one(users, {
|
|
2030
|
+
fields: [inviteCodeRedemptions.userId],
|
|
2031
|
+
references: [users.id]
|
|
2032
|
+
})
|
|
2033
|
+
})
|
|
2034
|
+
);
|
|
2035
|
+
var aiToolAuditLogsRelations = relations(aiToolAuditLogs, ({ one }) => ({
|
|
2036
|
+
company: one(companies, {
|
|
2037
|
+
fields: [aiToolAuditLogs.companyId],
|
|
2038
|
+
references: [companies.id]
|
|
2039
|
+
}),
|
|
2040
|
+
user: one(users, {
|
|
2041
|
+
fields: [aiToolAuditLogs.userId],
|
|
2042
|
+
references: [users.id]
|
|
2043
|
+
}),
|
|
2044
|
+
conversation: one(aiConversations, {
|
|
2045
|
+
fields: [aiToolAuditLogs.conversationId],
|
|
2046
|
+
references: [aiConversations.id]
|
|
2047
|
+
}),
|
|
2048
|
+
mcpConnection: one(mcpConnections, {
|
|
2049
|
+
fields: [aiToolAuditLogs.mcpConnectionId],
|
|
2050
|
+
references: [mcpConnections.id]
|
|
2051
|
+
})
|
|
2052
|
+
}));
|
|
2053
|
+
var mcpConnectionsRelations = relations(mcpConnections, ({ one, many }) => ({
|
|
2054
|
+
company: one(companies, {
|
|
2055
|
+
fields: [mcpConnections.companyId],
|
|
2056
|
+
references: [companies.id]
|
|
2057
|
+
}),
|
|
2058
|
+
ownerUser: one(users, {
|
|
2059
|
+
fields: [mcpConnections.ownerUserId],
|
|
2060
|
+
references: [users.id]
|
|
2061
|
+
}),
|
|
2062
|
+
credentials: many(mcpCredentials),
|
|
2063
|
+
toolPrefs: many(mcpToolPrefs)
|
|
2064
|
+
}));
|
|
2065
|
+
var mcpCredentialsRelations = relations(mcpCredentials, ({ one }) => ({
|
|
2066
|
+
mcpConnection: one(mcpConnections, {
|
|
2067
|
+
fields: [mcpCredentials.mcpConnectionId],
|
|
2068
|
+
references: [mcpConnections.id]
|
|
2069
|
+
})
|
|
2070
|
+
}));
|
|
2071
|
+
var mcpToolPrefsRelations = relations(mcpToolPrefs, ({ one }) => ({
|
|
2072
|
+
mcpConnection: one(mcpConnections, {
|
|
2073
|
+
fields: [mcpToolPrefs.mcpConnectionId],
|
|
2074
|
+
references: [mcpConnections.id]
|
|
2075
|
+
})
|
|
2076
|
+
}));
|
|
2077
|
+
var quickActionModeEnum = pgEnum("quick_action_mode", [
|
|
2078
|
+
"intelligence",
|
|
2079
|
+
"dynamic",
|
|
2080
|
+
"custom"
|
|
2081
|
+
]);
|
|
2082
|
+
var userPreferences = pgTable(
|
|
2083
|
+
"user_preferences",
|
|
2084
|
+
{
|
|
2085
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
2086
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
2087
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
2088
|
+
sidebarOrder: jsonb("sidebar_order").$type(),
|
|
2089
|
+
// ordered array of nav item IDs
|
|
2090
|
+
quickActionMode: quickActionModeEnum("quick_action_mode").notNull().default("dynamic"),
|
|
2091
|
+
quickActionModeOverrides: jsonb("quick_action_mode_overrides").$type(),
|
|
2092
|
+
// per-action mode overrides
|
|
2093
|
+
customQuickActions: jsonb("custom_quick_actions").$type(),
|
|
2094
|
+
// IDs of pinned quick actions
|
|
2095
|
+
sidebarCollapsed: boolean("sidebar_collapsed").notNull().default(false),
|
|
2096
|
+
/** D11: connection ids this user removed from THEIR AI context (AI-sidebar kill-switch). */
|
|
2097
|
+
disabledMcpConnections: jsonb("disabled_mcp_connections").$type(),
|
|
2098
|
+
/** S3b: built-in tool ids this user disabled from THEIR AI context (Tools-pane kill-switch). */
|
|
2099
|
+
disabledBuiltinTools: jsonb("disabled_builtin_tools").$type(),
|
|
2100
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
2101
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
2102
|
+
},
|
|
2103
|
+
(table) => [
|
|
2104
|
+
uniqueIndex("user_preferences_user_company_idx").on(
|
|
2105
|
+
table.userId,
|
|
2106
|
+
table.companyId
|
|
2107
|
+
)
|
|
2108
|
+
]
|
|
2109
|
+
);
|
|
2110
|
+
var userPreferencesRelations = relations(
|
|
2111
|
+
userPreferences,
|
|
2112
|
+
({ one }) => ({
|
|
2113
|
+
user: one(users, {
|
|
2114
|
+
fields: [userPreferences.userId],
|
|
2115
|
+
references: [users.id]
|
|
2116
|
+
}),
|
|
2117
|
+
company: one(companies, {
|
|
2118
|
+
fields: [userPreferences.companyId],
|
|
2119
|
+
references: [companies.id]
|
|
2120
|
+
})
|
|
2121
|
+
})
|
|
2122
|
+
);
|
|
2123
|
+
var aiPermissionDefaults = pgTable(
|
|
2124
|
+
"ai_permission_defaults",
|
|
2125
|
+
{
|
|
2126
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
2127
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
2128
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
2129
|
+
readMode: aiPermissionModeEnum("read_mode").notNull().default("always"),
|
|
2130
|
+
writeMode: aiPermissionModeEnum("write_mode").notNull().default("ask"),
|
|
2131
|
+
// Delete never offers "always" (enforced in app + resolver); column reuses the
|
|
2132
|
+
// shared enum but only "ask" / "session" are ever written.
|
|
2133
|
+
deleteMode: aiPermissionModeEnum("delete_mode").notNull().default("ask"),
|
|
2134
|
+
webSearchMode: aiPermissionModeEnum("web_search_mode").notNull().default("always"),
|
|
2135
|
+
browserUseMode: aiPermissionModeEnum("browser_use_mode").notNull().default("ask"),
|
|
2136
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
|
|
2137
|
+
updatedAt: timestamp("updated_at", { mode: "date" }).defaultNow().notNull().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
2138
|
+
},
|
|
2139
|
+
(table) => [
|
|
2140
|
+
uniqueIndex("ai_permission_defaults_user_company_idx").on(
|
|
2141
|
+
table.userId,
|
|
2142
|
+
table.companyId
|
|
2143
|
+
)
|
|
2144
|
+
]
|
|
2145
|
+
);
|
|
2146
|
+
var aiUsageLogs = pgTable(
|
|
2147
|
+
"ai_usage_logs",
|
|
2148
|
+
{
|
|
2149
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
2150
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
2151
|
+
feature: text("feature").notNull(),
|
|
2152
|
+
tier: text("tier").notNull(),
|
|
2153
|
+
provider: text("provider").notNull(),
|
|
2154
|
+
model: text("model").notNull(),
|
|
2155
|
+
inputTokens: integer("input_tokens").notNull(),
|
|
2156
|
+
outputTokens: integer("output_tokens").notNull(),
|
|
2157
|
+
estimatedCostMicros: integer("estimated_cost_micros").notNull().default(0),
|
|
2158
|
+
durationMs: integer("duration_ms"),
|
|
2159
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
2160
|
+
},
|
|
2161
|
+
(table) => [
|
|
2162
|
+
index("ai_usage_company_idx").on(table.companyId),
|
|
2163
|
+
index("ai_usage_feature_idx").on(table.companyId, table.feature),
|
|
2164
|
+
index("ai_usage_created_idx").on(table.companyId, table.createdAt)
|
|
2165
|
+
]
|
|
2166
|
+
);
|
|
2167
|
+
var exportLogs = pgTable(
|
|
2168
|
+
"export_logs",
|
|
2169
|
+
{
|
|
2170
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
2171
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
2172
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
2173
|
+
exportType: text("export_type").notNull(),
|
|
2174
|
+
format: text("format").notNull(),
|
|
2175
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
2176
|
+
},
|
|
2177
|
+
(table) => [
|
|
2178
|
+
index("export_logs_company_idx").on(table.companyId),
|
|
2179
|
+
index("export_logs_company_created_idx").on(table.companyId, table.createdAt),
|
|
2180
|
+
index("export_logs_user_idx").on(table.userId)
|
|
2181
|
+
]
|
|
2182
|
+
);
|
|
2183
|
+
var notifications = pgTable(
|
|
2184
|
+
"notifications",
|
|
2185
|
+
{
|
|
2186
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
2187
|
+
companyId: text("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
2188
|
+
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
2189
|
+
category: text("category").notNull(),
|
|
2190
|
+
title: text("title").notNull(),
|
|
2191
|
+
body: text("body"),
|
|
2192
|
+
severity: notificationSeverityEnum("severity").notNull().default("info"),
|
|
2193
|
+
link: text("link"),
|
|
2194
|
+
metadata: jsonb("metadata"),
|
|
2195
|
+
readAt: timestamp("read_at", { mode: "date" }),
|
|
2196
|
+
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull()
|
|
2197
|
+
},
|
|
2198
|
+
(table) => [
|
|
2199
|
+
index("notifications_user_idx").on(table.userId, table.companyId),
|
|
2200
|
+
index("notifications_unread_idx").on(table.userId, table.readAt),
|
|
2201
|
+
index("notifications_created_idx").on(table.createdAt)
|
|
2202
|
+
]
|
|
2203
|
+
);
|
|
2204
|
+
|
|
2205
|
+
// ../db/src/client/resolve.ts
|
|
2206
|
+
import { homedir } from "os";
|
|
2207
|
+
import { join } from "path";
|
|
2208
|
+
var BurnlessDbConfigError = class extends Error {
|
|
2209
|
+
constructor(message) {
|
|
2210
|
+
super(message);
|
|
2211
|
+
this.name = "BurnlessDbConfigError";
|
|
2212
|
+
}
|
|
2213
|
+
};
|
|
2214
|
+
var POSTGRES_URL = /^postgres(ql)?:\/\//i;
|
|
2215
|
+
function defaultDataDir(env) {
|
|
2216
|
+
return env.BURNLESS_DATA_DIR && env.BURNLESS_DATA_DIR.trim().length > 0 ? env.BURNLESS_DATA_DIR : join(homedir(), ".burnless", "data");
|
|
2217
|
+
}
|
|
2218
|
+
function resolveDriver(env) {
|
|
2219
|
+
const override = env.BURNLESS_DB_DRIVER?.trim().toLowerCase();
|
|
2220
|
+
const url = env.DATABASE_URL;
|
|
2221
|
+
const hasPgUrl = typeof url === "string" && POSTGRES_URL.test(url);
|
|
2222
|
+
if (override) {
|
|
2223
|
+
if (override === "postgres") {
|
|
2224
|
+
if (!hasPgUrl) {
|
|
2225
|
+
throw new BurnlessDbConfigError(
|
|
2226
|
+
"BURNLESS_DB_DRIVER=postgres requires a postgres(ql):// DATABASE_URL"
|
|
2227
|
+
);
|
|
2228
|
+
}
|
|
2229
|
+
return { driver: "postgres", connectionString: url };
|
|
2230
|
+
}
|
|
2231
|
+
if (override === "pglite") {
|
|
2232
|
+
return { driver: "pglite", dataDir: defaultDataDir(env) };
|
|
2233
|
+
}
|
|
2234
|
+
throw new BurnlessDbConfigError(
|
|
2235
|
+
`Unknown BURNLESS_DB_DRIVER="${env.BURNLESS_DB_DRIVER}" (expected "postgres" or "pglite")`
|
|
2236
|
+
);
|
|
2237
|
+
}
|
|
2238
|
+
if (hasPgUrl) return { driver: "postgres", connectionString: url };
|
|
2239
|
+
return { driver: "pglite", dataDir: defaultDataDir(env) };
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
// ../db/src/client/create.ts
|
|
2243
|
+
import { mkdirSync } from "fs";
|
|
2244
|
+
import { createRequire } from "module";
|
|
2245
|
+
import { pathToFileURL } from "url";
|
|
2246
|
+
import { PGlite } from "@electric-sql/pglite";
|
|
2247
|
+
function pgliteVectorExtension() {
|
|
2248
|
+
const fromEnv = process.env.BURNLESS_PGLITE_VECTOR_BUNDLE?.trim();
|
|
2249
|
+
const bundlePath = fromEnv ? pathToFileURL(fromEnv) : new URL(
|
|
2250
|
+
"./vector.tar.gz",
|
|
2251
|
+
pathToFileURL(
|
|
2252
|
+
createRequire(import.meta.url).resolve("@electric-sql/pglite-pgvector")
|
|
2253
|
+
)
|
|
2254
|
+
);
|
|
2255
|
+
return {
|
|
2256
|
+
name: "vector",
|
|
2257
|
+
setup: async (_pg, emscriptenOpts) => ({ emscriptenOpts, bundlePath })
|
|
2258
|
+
};
|
|
2259
|
+
}
|
|
2260
|
+
async function createClient(resolved) {
|
|
2261
|
+
if (resolved.driver === "postgres") {
|
|
2262
|
+
const { drizzle: drizzlePostgres } = await import("./postgres-js-SQJTBL3M.js");
|
|
2263
|
+
const { default: postgres } = await import("./src-LUBD7RKO.js");
|
|
2264
|
+
const client = postgres(resolved.connectionString, { max: 10 });
|
|
2265
|
+
return {
|
|
2266
|
+
db: drizzlePostgres(client, { schema: schema_exports }),
|
|
2267
|
+
dialect: "postgres",
|
|
2268
|
+
raw: client,
|
|
2269
|
+
close: () => client.end()
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
mkdirSync(resolved.dataDir, { recursive: true });
|
|
2273
|
+
const { drizzle: drizzlePglite } = await import("./pglite-KKF2QVEJ.js");
|
|
2274
|
+
const pglite = await PGlite.create(resolved.dataDir, {
|
|
2275
|
+
extensions: { vector: pgliteVectorExtension() }
|
|
2276
|
+
});
|
|
2277
|
+
const db2 = drizzlePglite(pglite, { schema: schema_exports });
|
|
2278
|
+
await db2.execute(sql`CREATE EXTENSION IF NOT EXISTS vector`);
|
|
2279
|
+
return {
|
|
2280
|
+
db: db2,
|
|
2281
|
+
dialect: "pglite",
|
|
2282
|
+
raw: pglite,
|
|
2283
|
+
close: () => pglite.close()
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
// ../db/src/client/migrate.ts
|
|
2288
|
+
import { dirname, join as join2 } from "path";
|
|
2289
|
+
import { fileURLToPath } from "url";
|
|
2290
|
+
|
|
2291
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.38.4_@electric-sql+pglite@0.5.2_@opentelemetry+api@1.9.0_@types+pg@8.15.6_75139cca41f10c6d94ac9ad65d4511ab/node_modules/drizzle-orm/migrator.js
|
|
2292
|
+
import crypto2 from "crypto";
|
|
2293
|
+
import fs from "fs";
|
|
2294
|
+
function readMigrationFiles(config) {
|
|
2295
|
+
const migrationFolderTo = config.migrationsFolder;
|
|
2296
|
+
const migrationQueries = [];
|
|
2297
|
+
const journalPath = `${migrationFolderTo}/meta/_journal.json`;
|
|
2298
|
+
if (!fs.existsSync(journalPath)) {
|
|
2299
|
+
throw new Error(`Can't find meta/_journal.json file`);
|
|
2300
|
+
}
|
|
2301
|
+
const journalAsString = fs.readFileSync(`${migrationFolderTo}/meta/_journal.json`).toString();
|
|
2302
|
+
const journal = JSON.parse(journalAsString);
|
|
2303
|
+
for (const journalEntry of journal.entries) {
|
|
2304
|
+
const migrationPath = `${migrationFolderTo}/${journalEntry.tag}.sql`;
|
|
2305
|
+
try {
|
|
2306
|
+
const query = fs.readFileSync(`${migrationFolderTo}/${journalEntry.tag}.sql`).toString();
|
|
2307
|
+
const result = query.split("--> statement-breakpoint").map((it) => {
|
|
2308
|
+
return it;
|
|
2309
|
+
});
|
|
2310
|
+
migrationQueries.push({
|
|
2311
|
+
sql: result,
|
|
2312
|
+
bps: journalEntry.breakpoints,
|
|
2313
|
+
folderMillis: journalEntry.when,
|
|
2314
|
+
hash: crypto2.createHash("sha256").update(query).digest("hex")
|
|
2315
|
+
});
|
|
2316
|
+
} catch {
|
|
2317
|
+
throw new Error(`No file ${migrationPath} found in ${migrationFolderTo} folder`);
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
return migrationQueries;
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.38.4_@electric-sql+pglite@0.5.2_@opentelemetry+api@1.9.0_@types+pg@8.15.6_75139cca41f10c6d94ac9ad65d4511ab/node_modules/drizzle-orm/pglite/migrator.js
|
|
2324
|
+
async function migrate(db2, config) {
|
|
2325
|
+
const migrations = readMigrationFiles(config);
|
|
2326
|
+
await db2.dialect.migrate(migrations, db2.session, config);
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
// ../../node_modules/.pnpm/drizzle-orm@0.38.4_@electric-sql+pglite@0.5.2_@opentelemetry+api@1.9.0_@types+pg@8.15.6_75139cca41f10c6d94ac9ad65d4511ab/node_modules/drizzle-orm/postgres-js/migrator.js
|
|
2330
|
+
async function migrate2(db2, config) {
|
|
2331
|
+
const migrations = readMigrationFiles(config);
|
|
2332
|
+
await db2.dialect.migrate(migrations, db2.session, config);
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
// ../db/src/client/migrate.ts
|
|
2336
|
+
function shouldAutoMigrate(dialect, env) {
|
|
2337
|
+
const raw = env.BURNLESS_AUTO_MIGRATE;
|
|
2338
|
+
if (typeof raw === "string" && raw.length > 0) return raw === "true";
|
|
2339
|
+
return dialect === "pglite";
|
|
2340
|
+
}
|
|
2341
|
+
function getMigrationsFolder() {
|
|
2342
|
+
const override = process.env.BURNLESS_MIGRATIONS_DIR;
|
|
2343
|
+
if (override && override.trim().length > 0) return override;
|
|
2344
|
+
return join2(dirname(fileURLToPath(import.meta.url)), "../../drizzle");
|
|
2345
|
+
}
|
|
2346
|
+
async function applyMigrations(handle) {
|
|
2347
|
+
const migrationsFolder = getMigrationsFolder();
|
|
2348
|
+
if (handle.dialect === "pglite") {
|
|
2349
|
+
await migrate(handle.db, { migrationsFolder });
|
|
2350
|
+
} else {
|
|
2351
|
+
await migrate2(handle.db, { migrationsFolder });
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
// ../db/src/queries/company.ts
|
|
2356
|
+
async function getCompanyForUser(userId) {
|
|
2357
|
+
const [membership] = await db.select({ companyId: companyMembers.companyId, role: companyMembers.role }).from(companyMembers).where(eq(companyMembers.userId, userId)).limit(1);
|
|
2358
|
+
return membership ?? null;
|
|
2359
|
+
}
|
|
2360
|
+
async function getUserWithCompany(userId) {
|
|
2361
|
+
const [row] = await db.select({
|
|
2362
|
+
id: users.id,
|
|
2363
|
+
email: users.email,
|
|
2364
|
+
name: users.name,
|
|
2365
|
+
image: users.image,
|
|
2366
|
+
companyId: companyMembers.companyId,
|
|
2367
|
+
role: companyMembers.role
|
|
2368
|
+
}).from(users).innerJoin(companyMembers, eq(companyMembers.userId, users.id)).where(eq(users.id, userId)).limit(1);
|
|
2369
|
+
return row ?? null;
|
|
2370
|
+
}
|
|
2371
|
+
async function getCompanyById(companyId) {
|
|
2372
|
+
const [row] = await db.select().from(companies).where(eq(companies.id, companyId)).limit(1);
|
|
2373
|
+
return row ?? null;
|
|
2374
|
+
}
|
|
2375
|
+
async function listCompaniesForUser(userId) {
|
|
2376
|
+
return db.select({
|
|
2377
|
+
companyId: companyMembers.companyId,
|
|
2378
|
+
role: companyMembers.role,
|
|
2379
|
+
name: companies.name
|
|
2380
|
+
}).from(companyMembers).innerJoin(companies, eq(companyMembers.companyId, companies.id)).where(eq(companyMembers.userId, userId));
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
// ../db/src/queries/scenario.ts
|
|
2384
|
+
var notDeleted = isNull(scenarios.deletedAt);
|
|
2385
|
+
async function getScenarioForCompany(scenarioId, companyId) {
|
|
2386
|
+
const [row] = await db.select().from(scenarios).where(and(eq(scenarios.id, scenarioId), eq(scenarios.companyId, companyId), notDeleted));
|
|
2387
|
+
return row ?? null;
|
|
2388
|
+
}
|
|
2389
|
+
async function getDefaultScenario(companyId) {
|
|
2390
|
+
const [row] = await db.select().from(scenarios).where(and(eq(scenarios.companyId, companyId), notDeleted)).orderBy(scenarios.createdAt).limit(1);
|
|
2391
|
+
return row ?? null;
|
|
2392
|
+
}
|
|
2393
|
+
async function getScenarioData(scenarioId, companyId) {
|
|
2394
|
+
const [fLines, accounts2, revStreams, hcPlans] = await Promise.all([
|
|
2395
|
+
db.select().from(forecastLines).where(eq(forecastLines.companyId, companyId)),
|
|
2396
|
+
db.select().from(financialAccounts).where(eq(financialAccounts.companyId, companyId)),
|
|
2397
|
+
db.select().from(revenueStreams).where(eq(revenueStreams.companyId, companyId)),
|
|
2398
|
+
db.select().from(headcountPlans).where(eq(headcountPlans.companyId, companyId))
|
|
2399
|
+
]);
|
|
2400
|
+
return { forecastLines: fLines, accounts: accounts2, revenueStreams: revStreams, headcountPlans: hcPlans };
|
|
2401
|
+
}
|
|
2402
|
+
async function getScenarioDataWithValues(scenarioId, companyId) {
|
|
2403
|
+
const base = await getScenarioData(scenarioId, companyId);
|
|
2404
|
+
const lineIds = base.forecastLines.map((l) => l.id);
|
|
2405
|
+
const [values, funding] = await Promise.all([
|
|
2406
|
+
lineIds.length > 0 ? db.select().from(forecastValues).where(inArray(forecastValues.forecastLineId, lineIds)) : Promise.resolve([]),
|
|
2407
|
+
db.select().from(fundingRounds).where(eq(fundingRounds.companyId, companyId))
|
|
2408
|
+
]);
|
|
2409
|
+
return { ...base, forecastValues: values, fundingRounds: funding };
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
// ../db/src/queries/crud.ts
|
|
2413
|
+
async function findByIdForCompany(table, id, companyId) {
|
|
2414
|
+
const [row] = await db.select().from(table).where(and(eq(table.id, id), eq(table.companyId, companyId))).limit(1);
|
|
2415
|
+
return row ?? null;
|
|
2416
|
+
}
|
|
2417
|
+
async function updateForCompany(table, id, companyId, data) {
|
|
2418
|
+
const [row] = await db.update(table).set(data).where(and(eq(table.id, id), eq(table.companyId, companyId))).returning();
|
|
2419
|
+
return row ?? null;
|
|
2420
|
+
}
|
|
2421
|
+
async function deleteForCompany(table, id, companyId) {
|
|
2422
|
+
const [row] = await db.delete(table).where(and(eq(table.id, id), eq(table.companyId, companyId))).returning();
|
|
2423
|
+
return row ?? null;
|
|
2424
|
+
}
|
|
2425
|
+
async function listForCompany(table, companyId) {
|
|
2426
|
+
const rows = await db.select().from(table).where(eq(table.companyId, companyId));
|
|
2427
|
+
return rows;
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
// ../db/src/queries/scenario-overrides.ts
|
|
2431
|
+
async function getOverridesForScenario(scenarioId, entityType) {
|
|
2432
|
+
const conditions = [eq(scenarioOverrides.scenarioId, scenarioId)];
|
|
2433
|
+
if (entityType) conditions.push(eq(scenarioOverrides.entityType, entityType));
|
|
2434
|
+
return db.select().from(scenarioOverrides).where(and(...conditions));
|
|
2435
|
+
}
|
|
2436
|
+
async function upsertOverride(scenarioId, entityType, entityId, action, data, originalData) {
|
|
2437
|
+
return db.insert(scenarioOverrides).values({
|
|
2438
|
+
scenarioId,
|
|
2439
|
+
entityType,
|
|
2440
|
+
entityId,
|
|
2441
|
+
action,
|
|
2442
|
+
data,
|
|
2443
|
+
originalData
|
|
2444
|
+
}).onConflictDoUpdate({
|
|
2445
|
+
target: [
|
|
2446
|
+
scenarioOverrides.scenarioId,
|
|
2447
|
+
scenarioOverrides.entityType,
|
|
2448
|
+
scenarioOverrides.entityId
|
|
2449
|
+
],
|
|
2450
|
+
set: { action, data, originalData, updatedAt: /* @__PURE__ */ new Date() }
|
|
2451
|
+
}).returning();
|
|
2452
|
+
}
|
|
2453
|
+
async function deleteOverride(overrideId) {
|
|
2454
|
+
return db.delete(scenarioOverrides).where(eq(scenarioOverrides.id, overrideId));
|
|
2455
|
+
}
|
|
2456
|
+
async function deleteOverrideByEntity(scenarioId, entityType, entityId) {
|
|
2457
|
+
return db.delete(scenarioOverrides).where(
|
|
2458
|
+
and(
|
|
2459
|
+
eq(scenarioOverrides.scenarioId, scenarioId),
|
|
2460
|
+
eq(scenarioOverrides.entityType, entityType),
|
|
2461
|
+
eq(scenarioOverrides.entityId, entityId)
|
|
2462
|
+
)
|
|
2463
|
+
);
|
|
2464
|
+
}
|
|
2465
|
+
async function getOverrideCount(scenarioId) {
|
|
2466
|
+
const [result] = await db.select({ count: sql`count(*)::int` }).from(scenarioOverrides).where(eq(scenarioOverrides.scenarioId, scenarioId));
|
|
2467
|
+
return result?.count ?? 0;
|
|
2468
|
+
}
|
|
2469
|
+
async function getOverrideCounts(scenarioIds) {
|
|
2470
|
+
if (scenarioIds.length === 0) return /* @__PURE__ */ new Map();
|
|
2471
|
+
const rows = await db.select({
|
|
2472
|
+
scenarioId: scenarioOverrides.scenarioId,
|
|
2473
|
+
count: sql`count(*)::int`
|
|
2474
|
+
}).from(scenarioOverrides).where(inArray(scenarioOverrides.scenarioId, scenarioIds)).groupBy(scenarioOverrides.scenarioId);
|
|
2475
|
+
const map = /* @__PURE__ */ new Map();
|
|
2476
|
+
for (const r of rows) map.set(r.scenarioId, r.count);
|
|
2477
|
+
return map;
|
|
2478
|
+
}
|
|
2479
|
+
async function getOverrideBreakdown(scenarioIds) {
|
|
2480
|
+
if (scenarioIds.length === 0) return [];
|
|
2481
|
+
return db.select({
|
|
2482
|
+
scenarioId: scenarioOverrides.scenarioId,
|
|
2483
|
+
entityType: scenarioOverrides.entityType,
|
|
2484
|
+
action: scenarioOverrides.action,
|
|
2485
|
+
count: sql`count(*)::int`
|
|
2486
|
+
}).from(scenarioOverrides).where(inArray(scenarioOverrides.scenarioId, scenarioIds)).groupBy(scenarioOverrides.scenarioId, scenarioOverrides.entityType, scenarioOverrides.action);
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
// ../db/src/queries/funding.ts
|
|
2490
|
+
async function listInvestorsForRound(fundingRoundId) {
|
|
2491
|
+
return db.select().from(fundingRoundInvestors).where(eq(fundingRoundInvestors.fundingRoundId, fundingRoundId)).orderBy(asc(fundingRoundInvestors.createdAt));
|
|
2492
|
+
}
|
|
2493
|
+
async function listShareClasses(companyId) {
|
|
2494
|
+
return db.select().from(shareClasses).where(and(eq(shareClasses.companyId, companyId), isNull(shareClasses.deletedAt))).orderBy(asc(shareClasses.createdAt));
|
|
2495
|
+
}
|
|
2496
|
+
async function listOptionPools(companyId) {
|
|
2497
|
+
return db.select().from(optionPools).where(and(eq(optionPools.companyId, companyId), isNull(optionPools.deletedAt))).orderBy(asc(optionPools.createdAt));
|
|
2498
|
+
}
|
|
2499
|
+
async function createShareClass(companyId, data) {
|
|
2500
|
+
const [row] = await db.insert(shareClasses).values({ ...data, companyId }).returning();
|
|
2501
|
+
return row;
|
|
2502
|
+
}
|
|
2503
|
+
async function updateShareClass(id, companyId, changes) {
|
|
2504
|
+
return updateForCompany(shareClasses, id, companyId, changes);
|
|
2505
|
+
}
|
|
2506
|
+
async function softDeleteShareClass(id, companyId) {
|
|
2507
|
+
return updateForCompany(shareClasses, id, companyId, { deletedAt: /* @__PURE__ */ new Date() });
|
|
2508
|
+
}
|
|
2509
|
+
async function createOptionPool(companyId, data) {
|
|
2510
|
+
const [row] = await db.insert(optionPools).values({ ...data, companyId }).returning();
|
|
2511
|
+
return row;
|
|
2512
|
+
}
|
|
2513
|
+
async function updateOptionPool(id, companyId, changes) {
|
|
2514
|
+
return updateForCompany(optionPools, id, companyId, changes);
|
|
2515
|
+
}
|
|
2516
|
+
async function softDeleteOptionPool(id, companyId) {
|
|
2517
|
+
return updateForCompany(optionPools, id, companyId, { deletedAt: /* @__PURE__ */ new Date() });
|
|
2518
|
+
}
|
|
2519
|
+
async function countOptionPools(companyId) {
|
|
2520
|
+
const [result] = await db.select({ count: sql`count(*)::int` }).from(optionPools).where(and(eq(optionPools.companyId, companyId), isNull(optionPools.deletedAt)));
|
|
2521
|
+
return result?.count ?? 0;
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
// ../db/src/queries/scenario-resolver.ts
|
|
2525
|
+
async function resolveEntities(entityType, baseEntities, scenarioId) {
|
|
2526
|
+
if (!scenarioId) {
|
|
2527
|
+
return baseEntities.map((e) => ({ ...e, _override: null }));
|
|
2528
|
+
}
|
|
2529
|
+
const overrides = await db.select().from(scenarioOverrides).where(
|
|
2530
|
+
and(
|
|
2531
|
+
eq(scenarioOverrides.scenarioId, scenarioId),
|
|
2532
|
+
eq(scenarioOverrides.entityType, entityType)
|
|
2533
|
+
)
|
|
2534
|
+
);
|
|
2535
|
+
const overrideMap = new Map(overrides.map((o) => [o.entityId, o]));
|
|
2536
|
+
const matchedOverrideIds = /* @__PURE__ */ new Set();
|
|
2537
|
+
const result = [];
|
|
2538
|
+
for (const entity of baseEntities) {
|
|
2539
|
+
const override = overrideMap.get(entity.id);
|
|
2540
|
+
if (!override) {
|
|
2541
|
+
result.push({ ...entity, _override: null });
|
|
2542
|
+
} else if (override.action === "modify") {
|
|
2543
|
+
matchedOverrideIds.add(override.id);
|
|
2544
|
+
result.push({ ...override.data, _override: "modified" });
|
|
2545
|
+
} else if (override.action === "delete") {
|
|
2546
|
+
matchedOverrideIds.add(override.id);
|
|
2547
|
+
continue;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
for (const o of overrides.filter((o2) => o2.action === "create")) {
|
|
2551
|
+
result.push({ ...o.data, _override: "created" });
|
|
2552
|
+
}
|
|
2553
|
+
for (const o of overrides.filter(
|
|
2554
|
+
(o2) => o2.action === "modify" && !matchedOverrideIds.has(o2.id)
|
|
2555
|
+
)) {
|
|
2556
|
+
result.push({ ...o.data, _override: "created" });
|
|
2557
|
+
}
|
|
2558
|
+
return result;
|
|
2559
|
+
}
|
|
2560
|
+
async function getResolvedData(companyId, scenarioId) {
|
|
2561
|
+
const [
|
|
2562
|
+
baseRevenue,
|
|
2563
|
+
baseHeadcount,
|
|
2564
|
+
baseForecast,
|
|
2565
|
+
baseFunding,
|
|
2566
|
+
baseDepts,
|
|
2567
|
+
baseAccounts,
|
|
2568
|
+
baseShareClasses,
|
|
2569
|
+
baseOptionPools
|
|
2570
|
+
] = await Promise.all([
|
|
2571
|
+
db.select().from(revenueStreams).where(eq(revenueStreams.companyId, companyId)),
|
|
2572
|
+
db.select().from(headcountPlans).where(eq(headcountPlans.companyId, companyId)),
|
|
2573
|
+
db.select().from(forecastLines).where(eq(forecastLines.companyId, companyId)),
|
|
2574
|
+
db.select().from(fundingRounds).where(eq(fundingRounds.companyId, companyId)),
|
|
2575
|
+
db.select().from(departments).where(eq(departments.companyId, companyId)),
|
|
2576
|
+
db.select().from(financialAccounts).where(eq(financialAccounts.companyId, companyId)),
|
|
2577
|
+
listShareClasses(companyId),
|
|
2578
|
+
listOptionPools(companyId)
|
|
2579
|
+
]);
|
|
2580
|
+
const roundIds = baseFunding.map((r) => r.id);
|
|
2581
|
+
const baseInvestors = roundIds.length > 0 ? await db.select().from(fundingRoundInvestors).where(inArray(fundingRoundInvestors.fundingRoundId, roundIds)) : [];
|
|
2582
|
+
const [
|
|
2583
|
+
revenueResolved,
|
|
2584
|
+
headcountResolved,
|
|
2585
|
+
forecastResolved,
|
|
2586
|
+
fundingResolved,
|
|
2587
|
+
deptsResolved,
|
|
2588
|
+
accountsResolved
|
|
2589
|
+
] = await Promise.all([
|
|
2590
|
+
resolveEntities("revenue_stream", baseRevenue, scenarioId),
|
|
2591
|
+
resolveEntities("headcount_plan", baseHeadcount, scenarioId),
|
|
2592
|
+
resolveEntities("forecast_line", baseForecast, scenarioId),
|
|
2593
|
+
resolveEntities("funding_round", baseFunding, scenarioId),
|
|
2594
|
+
resolveEntities("department", baseDepts, scenarioId),
|
|
2595
|
+
resolveEntities("financial_account", baseAccounts, scenarioId)
|
|
2596
|
+
]);
|
|
2597
|
+
return {
|
|
2598
|
+
revenueStreams: revenueResolved,
|
|
2599
|
+
headcountPlans: headcountResolved,
|
|
2600
|
+
forecastLines: forecastResolved,
|
|
2601
|
+
fundingRounds: fundingResolved,
|
|
2602
|
+
departments: deptsResolved,
|
|
2603
|
+
financialAccounts: accountsResolved,
|
|
2604
|
+
fundingRoundInvestors: baseInvestors.filter(
|
|
2605
|
+
(i) => fundingResolved.some((r) => r.id === i.fundingRoundId)
|
|
2606
|
+
),
|
|
2607
|
+
shareClasses: baseShareClasses,
|
|
2608
|
+
optionPools: baseOptionPools
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
// ../db/src/queries/scenario-mutations.ts
|
|
2613
|
+
async function assertScenarioOwned(scenarioId, companyId) {
|
|
2614
|
+
const [owned] = await db.select({ id: scenarios.id }).from(scenarios).where(and(eq(scenarios.id, scenarioId), eq(scenarios.companyId, companyId))).limit(1);
|
|
2615
|
+
if (!owned) {
|
|
2616
|
+
throw new Error(`Scenario ${scenarioId} not found for company ${companyId}`);
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
var MERGABLE_JSONB_KEYS = /* @__PURE__ */ new Set(["parameters"]);
|
|
2620
|
+
function isPlainObject(value) {
|
|
2621
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
2622
|
+
}
|
|
2623
|
+
function mergeChanges(base, changes) {
|
|
2624
|
+
const merged = { ...base, ...changes };
|
|
2625
|
+
for (const key of MERGABLE_JSONB_KEYS) {
|
|
2626
|
+
const baseVal = base[key];
|
|
2627
|
+
const changeVal = changes[key];
|
|
2628
|
+
if (isPlainObject(baseVal) && isPlainObject(changeVal)) {
|
|
2629
|
+
merged[key] = { ...baseVal, ...changeVal };
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
return merged;
|
|
2633
|
+
}
|
|
2634
|
+
function validateOverridable(entityType, baseEntity) {
|
|
2635
|
+
if (entityType === "financial_account" && baseEntity?.isSystem) {
|
|
2636
|
+
throw new Error("System accounts cannot be modified in scenarios");
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
async function planScenarioInsert(entityType, table, data, scenarioId, companyId) {
|
|
2640
|
+
if (!scenarioId) throw new Error("planScenarioInsert requires an active scenario");
|
|
2641
|
+
await assertScenarioOwned(scenarioId, companyId);
|
|
2642
|
+
const id = data.id ?? crypto.randomUUID();
|
|
2643
|
+
const entityData = { ...data, id };
|
|
2644
|
+
return { action: "create", entityType, entityId: id, before: null, after: entityData };
|
|
2645
|
+
}
|
|
2646
|
+
async function commitScenarioPlan(scenarioId, plan) {
|
|
2647
|
+
if (plan.action === "remove_override") {
|
|
2648
|
+
await deleteOverrideByEntity(scenarioId, plan.entityType, plan.entityId);
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2651
|
+
await upsertOverride(scenarioId, plan.entityType, plan.entityId, plan.action, plan.after, plan.before);
|
|
2652
|
+
}
|
|
2653
|
+
async function planScenarioUpdate(entityType, table, entityId, changes, scenarioId, companyId) {
|
|
2654
|
+
if (!scenarioId) throw new Error("planScenarioUpdate requires an active scenario");
|
|
2655
|
+
if (entityType === "funding_round" && changes && "type" in changes) {
|
|
2656
|
+
const { type: _stripped, ...rest } = changes;
|
|
2657
|
+
changes = rest;
|
|
2658
|
+
}
|
|
2659
|
+
await assertScenarioOwned(scenarioId, companyId);
|
|
2660
|
+
const existing = await db.select().from(scenarioOverrides).where(
|
|
2661
|
+
and(
|
|
2662
|
+
eq(scenarioOverrides.scenarioId, scenarioId),
|
|
2663
|
+
eq(scenarioOverrides.entityType, entityType),
|
|
2664
|
+
eq(scenarioOverrides.entityId, entityId)
|
|
2665
|
+
)
|
|
2666
|
+
).then((r) => r[0]);
|
|
2667
|
+
if (existing) {
|
|
2668
|
+
const after2 = mergeChanges(
|
|
2669
|
+
existing.data,
|
|
2670
|
+
changes
|
|
2671
|
+
);
|
|
2672
|
+
return {
|
|
2673
|
+
action: existing.action,
|
|
2674
|
+
entityType,
|
|
2675
|
+
entityId,
|
|
2676
|
+
before: existing.originalData ?? null,
|
|
2677
|
+
after: after2
|
|
2678
|
+
};
|
|
2679
|
+
}
|
|
2680
|
+
const [baseEntity] = await db.select().from(table).where(and(eq(table.id, entityId), eq(table.companyId, companyId)));
|
|
2681
|
+
if (!baseEntity) throw new Error(`Entity ${entityType}/${entityId} not found`);
|
|
2682
|
+
validateOverridable(entityType, baseEntity);
|
|
2683
|
+
const after = mergeChanges(
|
|
2684
|
+
baseEntity,
|
|
2685
|
+
changes
|
|
2686
|
+
);
|
|
2687
|
+
return {
|
|
2688
|
+
action: "modify",
|
|
2689
|
+
entityType,
|
|
2690
|
+
entityId,
|
|
2691
|
+
before: baseEntity,
|
|
2692
|
+
after
|
|
2693
|
+
};
|
|
2694
|
+
}
|
|
2695
|
+
async function planScenarioDelete(entityType, table, entityId, scenarioId, companyId) {
|
|
2696
|
+
if (!scenarioId) throw new Error("planScenarioDelete requires an active scenario");
|
|
2697
|
+
await assertScenarioOwned(scenarioId, companyId);
|
|
2698
|
+
const existing = await db.select().from(scenarioOverrides).where(
|
|
2699
|
+
and(
|
|
2700
|
+
eq(scenarioOverrides.scenarioId, scenarioId),
|
|
2701
|
+
eq(scenarioOverrides.entityType, entityType),
|
|
2702
|
+
eq(scenarioOverrides.entityId, entityId)
|
|
2703
|
+
)
|
|
2704
|
+
).then((r) => r[0]);
|
|
2705
|
+
if (existing?.action === "create") {
|
|
2706
|
+
return [
|
|
2707
|
+
{
|
|
2708
|
+
action: "remove_override",
|
|
2709
|
+
entityType,
|
|
2710
|
+
entityId,
|
|
2711
|
+
before: existing.data ?? null,
|
|
2712
|
+
after: null
|
|
2713
|
+
}
|
|
2714
|
+
];
|
|
2715
|
+
}
|
|
2716
|
+
const [baseEntity] = await db.select().from(table).where(and(eq(table.id, entityId), eq(table.companyId, companyId)));
|
|
2717
|
+
if (!baseEntity) throw new Error(`Entity ${entityType}/${entityId} not found`);
|
|
2718
|
+
validateOverridable(entityType, baseEntity);
|
|
2719
|
+
const plans = [
|
|
2720
|
+
{
|
|
2721
|
+
action: "delete",
|
|
2722
|
+
entityType,
|
|
2723
|
+
entityId,
|
|
2724
|
+
before: baseEntity,
|
|
2725
|
+
after: null
|
|
2726
|
+
}
|
|
2727
|
+
];
|
|
2728
|
+
if (entityType === "department") {
|
|
2729
|
+
const children = await db.select().from(departments).where(eq(departments.parentId, entityId));
|
|
2730
|
+
for (const child of children) {
|
|
2731
|
+
plans.push(
|
|
2732
|
+
...await planScenarioDelete("department", departments, child.id, scenarioId, companyId)
|
|
2733
|
+
);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
return plans;
|
|
2737
|
+
}
|
|
2738
|
+
async function scenarioInsert(entityType, table, data, scenarioId, companyId) {
|
|
2739
|
+
if (!scenarioId) {
|
|
2740
|
+
const [row] = await db.insert(table).values(data).returning();
|
|
2741
|
+
return row;
|
|
2742
|
+
}
|
|
2743
|
+
const plan = await planScenarioInsert(entityType, table, data, scenarioId, companyId);
|
|
2744
|
+
await commitScenarioPlan(scenarioId, plan);
|
|
2745
|
+
return plan.after;
|
|
2746
|
+
}
|
|
2747
|
+
async function scenarioUpdate(entityType, table, entityId, changes, scenarioId, companyId) {
|
|
2748
|
+
if (!scenarioId) {
|
|
2749
|
+
if (entityType === "funding_round" && changes && "type" in changes) {
|
|
2750
|
+
const { type: _stripped, ...rest } = changes;
|
|
2751
|
+
changes = rest;
|
|
2752
|
+
}
|
|
2753
|
+
const [row] = await db.update(table).set(changes).where(and(eq(table.id, entityId), eq(table.companyId, companyId))).returning();
|
|
2754
|
+
return row;
|
|
2755
|
+
}
|
|
2756
|
+
const plan = await planScenarioUpdate(entityType, table, entityId, changes, scenarioId, companyId);
|
|
2757
|
+
await commitScenarioPlan(scenarioId, plan);
|
|
2758
|
+
return plan.after;
|
|
2759
|
+
}
|
|
2760
|
+
async function scenarioDelete(entityType, table, entityId, scenarioId, companyId) {
|
|
2761
|
+
if (!scenarioId) {
|
|
2762
|
+
const deleted = await db.delete(table).where(and(eq(table.id, entityId), eq(table.companyId, companyId))).returning();
|
|
2763
|
+
if (deleted.length > 0) {
|
|
2764
|
+
await db.delete(scenarioOverrides).where(
|
|
2765
|
+
and(
|
|
2766
|
+
eq(scenarioOverrides.entityType, entityType),
|
|
2767
|
+
eq(scenarioOverrides.entityId, entityId)
|
|
2768
|
+
)
|
|
2769
|
+
);
|
|
2770
|
+
}
|
|
2771
|
+
return deleted.length > 0;
|
|
2772
|
+
}
|
|
2773
|
+
const plans = await planScenarioDelete(entityType, table, entityId, scenarioId, companyId);
|
|
2774
|
+
for (const plan of plans) {
|
|
2775
|
+
await commitScenarioPlan(scenarioId, plan);
|
|
2776
|
+
}
|
|
2777
|
+
return true;
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
// ../db/src/queries/scenario-promotion.ts
|
|
2781
|
+
function getTableForEntityType(entityType) {
|
|
2782
|
+
const map = {
|
|
2783
|
+
revenue_stream: revenueStreams,
|
|
2784
|
+
headcount_plan: headcountPlans,
|
|
2785
|
+
forecast_line: forecastLines,
|
|
2786
|
+
funding_round: fundingRounds,
|
|
2787
|
+
department: departments,
|
|
2788
|
+
financial_account: financialAccounts,
|
|
2789
|
+
salary_change: salaryChanges,
|
|
2790
|
+
bonus: bonuses,
|
|
2791
|
+
equity_grant: equityGrants
|
|
2792
|
+
};
|
|
2793
|
+
const table = map[entityType];
|
|
2794
|
+
if (!table) throw new Error(`Unknown entity type: ${entityType}`);
|
|
2795
|
+
return table;
|
|
2796
|
+
}
|
|
2797
|
+
function rehydrateForTable(table, data) {
|
|
2798
|
+
const result = { ...data };
|
|
2799
|
+
const columns = table[/* @__PURE__ */ Symbol.for("drizzle:Columns")] ?? table;
|
|
2800
|
+
for (const [key, col] of Object.entries(columns)) {
|
|
2801
|
+
if (col instanceof PgTimestamp && typeof result[key] === "string") {
|
|
2802
|
+
result[key] = new Date(result[key]);
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
return result;
|
|
2806
|
+
}
|
|
2807
|
+
async function promoteScenario(scenarioId, companyId) {
|
|
2808
|
+
return db.transaction(async (tx) => {
|
|
2809
|
+
const [backup] = await tx.insert(scenarios).values({
|
|
2810
|
+
companyId,
|
|
2811
|
+
name: `Backup (pre-promote, ${(/* @__PURE__ */ new Date()).toLocaleDateString()})`,
|
|
2812
|
+
source: "backup",
|
|
2813
|
+
status: "active",
|
|
2814
|
+
sourceScenarioId: scenarioId,
|
|
2815
|
+
autoDeleteAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3)
|
|
2816
|
+
// 7 days
|
|
2817
|
+
}).returning();
|
|
2818
|
+
const affectedTypes = await tx.selectDistinct({ entityType: scenarioOverrides.entityType }).from(scenarioOverrides).where(eq(scenarioOverrides.scenarioId, scenarioId));
|
|
2819
|
+
for (const { entityType } of affectedTypes) {
|
|
2820
|
+
const table = getTableForEntityType(entityType);
|
|
2821
|
+
const baseEntities = await tx.select().from(table).where(eq(table.companyId, companyId));
|
|
2822
|
+
for (const entity of baseEntities) {
|
|
2823
|
+
await tx.insert(scenarioOverrides).values({
|
|
2824
|
+
scenarioId: backup.id,
|
|
2825
|
+
entityType,
|
|
2826
|
+
entityId: entity.id,
|
|
2827
|
+
action: "modify",
|
|
2828
|
+
data: entity,
|
|
2829
|
+
originalData: entity
|
|
2830
|
+
});
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
const overrides = await tx.select().from(scenarioOverrides).where(eq(scenarioOverrides.scenarioId, scenarioId));
|
|
2834
|
+
for (const override of overrides) {
|
|
2835
|
+
const table = getTableForEntityType(override.entityType);
|
|
2836
|
+
if (override.action === "modify") {
|
|
2837
|
+
const hydrated = rehydrateForTable(table, override.data);
|
|
2838
|
+
await tx.update(table).set(hydrated).where(eq(table.id, override.entityId));
|
|
2839
|
+
const [exists] = await tx.select({ id: table.id }).from(table).where(eq(table.id, override.entityId));
|
|
2840
|
+
if (!exists) {
|
|
2841
|
+
await tx.insert(table).values(hydrated);
|
|
2842
|
+
}
|
|
2843
|
+
} else if (override.action === "create") {
|
|
2844
|
+
const hydrated = rehydrateForTable(table, override.data);
|
|
2845
|
+
await tx.insert(table).values(hydrated);
|
|
2846
|
+
} else if (override.action === "delete") {
|
|
2847
|
+
await tx.delete(table).where(eq(table.id, override.entityId));
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
await tx.update(scenarios).set({
|
|
2851
|
+
status: "promoted",
|
|
2852
|
+
promotedAt: /* @__PURE__ */ new Date()
|
|
2853
|
+
}).where(eq(scenarios.id, scenarioId));
|
|
2854
|
+
return { backup, promotedScenarioId: scenarioId };
|
|
2855
|
+
});
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
// ../db/src/queries/company-financial-data.ts
|
|
2859
|
+
async function hasFinancialData(companyId) {
|
|
2860
|
+
const [row] = await db.select({
|
|
2861
|
+
has: sql`(
|
|
2862
|
+
EXISTS(SELECT 1 FROM revenue_streams WHERE company_id = ${companyId})
|
|
2863
|
+
OR EXISTS(SELECT 1 FROM transactions WHERE company_id = ${companyId})
|
|
2864
|
+
OR EXISTS(SELECT 1 FROM headcount_plans WHERE company_id = ${companyId})
|
|
2865
|
+
OR EXISTS(SELECT 1 FROM funding_rounds WHERE company_id = ${companyId})
|
|
2866
|
+
)`
|
|
2867
|
+
}).from(companies).where(eq(companies.id, companyId)).limit(1);
|
|
2868
|
+
return row?.has === true;
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
// ../db/src/queries/entity-types.ts
|
|
2872
|
+
var SCENARIO_ENTITY_TYPES = [
|
|
2873
|
+
"revenue_stream",
|
|
2874
|
+
"transaction",
|
|
2875
|
+
"forecast_line",
|
|
2876
|
+
"headcount_plan",
|
|
2877
|
+
"funding_round",
|
|
2878
|
+
"department",
|
|
2879
|
+
"financial_account",
|
|
2880
|
+
"salary_change",
|
|
2881
|
+
"bonus",
|
|
2882
|
+
"equity_grant"
|
|
2883
|
+
];
|
|
2884
|
+
|
|
2885
|
+
// ../db/src/queries/salary-changes.ts
|
|
2886
|
+
async function listSalaryChanges(companyId, headcountId) {
|
|
2887
|
+
return db.select().from(salaryChanges).where(
|
|
2888
|
+
and(
|
|
2889
|
+
eq(salaryChanges.companyId, companyId),
|
|
2890
|
+
eq(salaryChanges.headcountId, headcountId)
|
|
2891
|
+
)
|
|
2892
|
+
);
|
|
2893
|
+
}
|
|
2894
|
+
async function listResolvedSalaryChanges(companyId, headcountId, scenarioId) {
|
|
2895
|
+
const base = await listSalaryChanges(companyId, headcountId);
|
|
2896
|
+
const resolved = await resolveEntities(
|
|
2897
|
+
"salary_change",
|
|
2898
|
+
base,
|
|
2899
|
+
scenarioId
|
|
2900
|
+
);
|
|
2901
|
+
return resolved.filter((r) => r.headcountId === headcountId);
|
|
2902
|
+
}
|
|
2903
|
+
async function createSalaryChange(data, scenarioId, companyId) {
|
|
2904
|
+
return scenarioInsert("salary_change", salaryChanges, data, scenarioId, companyId);
|
|
2905
|
+
}
|
|
2906
|
+
async function updateSalaryChange(id, changes, scenarioId, companyId) {
|
|
2907
|
+
return scenarioUpdate("salary_change", salaryChanges, id, changes, scenarioId, companyId);
|
|
2908
|
+
}
|
|
2909
|
+
async function removeSalaryChange(id, scenarioId, companyId) {
|
|
2910
|
+
return scenarioDelete("salary_change", salaryChanges, id, scenarioId, companyId);
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
// ../db/src/queries/bonuses.ts
|
|
2914
|
+
async function listBonuses(companyId, headcountId) {
|
|
2915
|
+
return db.select().from(bonuses).where(
|
|
2916
|
+
and(
|
|
2917
|
+
eq(bonuses.companyId, companyId),
|
|
2918
|
+
eq(bonuses.headcountId, headcountId)
|
|
2919
|
+
)
|
|
2920
|
+
);
|
|
2921
|
+
}
|
|
2922
|
+
async function listResolvedBonuses(companyId, headcountId, scenarioId) {
|
|
2923
|
+
const base = await listBonuses(companyId, headcountId);
|
|
2924
|
+
const resolved = await resolveEntities("bonus", base, scenarioId);
|
|
2925
|
+
return resolved.filter((r) => r.headcountId === headcountId);
|
|
2926
|
+
}
|
|
2927
|
+
async function createBonus(data, scenarioId, companyId) {
|
|
2928
|
+
return scenarioInsert("bonus", bonuses, data, scenarioId, companyId);
|
|
2929
|
+
}
|
|
2930
|
+
async function updateBonus(id, changes, scenarioId, companyId) {
|
|
2931
|
+
return scenarioUpdate("bonus", bonuses, id, changes, scenarioId, companyId);
|
|
2932
|
+
}
|
|
2933
|
+
async function removeBonus(id, scenarioId, companyId) {
|
|
2934
|
+
return scenarioDelete("bonus", bonuses, id, scenarioId, companyId);
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
// ../db/src/queries/equity-grants.ts
|
|
2938
|
+
async function listEquityGrants(companyId, headcountId) {
|
|
2939
|
+
return db.select().from(equityGrants).where(
|
|
2940
|
+
and(
|
|
2941
|
+
eq(equityGrants.companyId, companyId),
|
|
2942
|
+
eq(equityGrants.headcountId, headcountId)
|
|
2943
|
+
)
|
|
2944
|
+
);
|
|
2945
|
+
}
|
|
2946
|
+
async function listResolvedEquityGrants(companyId, headcountId, scenarioId) {
|
|
2947
|
+
const base = await listEquityGrants(companyId, headcountId);
|
|
2948
|
+
const resolved = await resolveEntities(
|
|
2949
|
+
"equity_grant",
|
|
2950
|
+
base,
|
|
2951
|
+
scenarioId
|
|
2952
|
+
);
|
|
2953
|
+
return resolved.filter((r) => r.headcountId === headcountId);
|
|
2954
|
+
}
|
|
2955
|
+
async function createEquityGrant(data, scenarioId, companyId) {
|
|
2956
|
+
return scenarioInsert("equity_grant", equityGrants, data, scenarioId, companyId);
|
|
2957
|
+
}
|
|
2958
|
+
async function updateEquityGrant(id, changes, scenarioId, companyId) {
|
|
2959
|
+
return scenarioUpdate(
|
|
2960
|
+
"equity_grant",
|
|
2961
|
+
equityGrants,
|
|
2962
|
+
id,
|
|
2963
|
+
changes,
|
|
2964
|
+
scenarioId,
|
|
2965
|
+
companyId
|
|
2966
|
+
);
|
|
2967
|
+
}
|
|
2968
|
+
async function removeEquityGrant(id, scenarioId, companyId) {
|
|
2969
|
+
return scenarioDelete("equity_grant", equityGrants, id, scenarioId, companyId);
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
// ../db/src/queries/ai-permissions.ts
|
|
2973
|
+
async function getPermissionDefaults(userId, companyId) {
|
|
2974
|
+
const [row] = await db.select().from(aiPermissionDefaults).where(
|
|
2975
|
+
and(
|
|
2976
|
+
eq(aiPermissionDefaults.userId, userId),
|
|
2977
|
+
eq(aiPermissionDefaults.companyId, companyId)
|
|
2978
|
+
)
|
|
2979
|
+
).limit(1);
|
|
2980
|
+
return row ?? null;
|
|
2981
|
+
}
|
|
2982
|
+
async function upsertPermissionDefaults(userId, companyId, patch) {
|
|
2983
|
+
await db.insert(aiPermissionDefaults).values({ userId, companyId, ...patch }).onConflictDoUpdate({
|
|
2984
|
+
target: [aiPermissionDefaults.userId, aiPermissionDefaults.companyId],
|
|
2985
|
+
set: { ...patch, updatedAt: /* @__PURE__ */ new Date() }
|
|
2986
|
+
});
|
|
2987
|
+
}
|
|
2988
|
+
async function getSessionGrants(conversationId) {
|
|
2989
|
+
const [row] = await db.select({ grants: aiConversations.sessionGrants }).from(aiConversations).where(eq(aiConversations.id, conversationId)).limit(1);
|
|
2990
|
+
return row?.grants ?? {};
|
|
2991
|
+
}
|
|
2992
|
+
async function grantSessionPermission(conversationId, category) {
|
|
2993
|
+
const current = await getSessionGrants(conversationId);
|
|
2994
|
+
const next = { ...current, [category]: true };
|
|
2995
|
+
await db.update(aiConversations).set({ sessionGrants: next }).where(eq(aiConversations.id, conversationId));
|
|
2996
|
+
}
|
|
2997
|
+
async function resetSessionGrants(conversationId) {
|
|
2998
|
+
await db.update(aiConversations).set({ sessionGrants: {} }).where(eq(aiConversations.id, conversationId));
|
|
2999
|
+
}
|
|
3000
|
+
async function getSessionDisabledTools(conversationId) {
|
|
3001
|
+
const [row] = await db.select({ disabled: aiConversations.sessionDisabledTools }).from(aiConversations).where(eq(aiConversations.id, conversationId)).limit(1);
|
|
3002
|
+
return row?.disabled ?? {};
|
|
3003
|
+
}
|
|
3004
|
+
async function setSessionDisabledTool(conversationId, key, disabled) {
|
|
3005
|
+
const current = await getSessionDisabledTools(conversationId);
|
|
3006
|
+
const next = { ...current };
|
|
3007
|
+
if (disabled) next[key] = true;
|
|
3008
|
+
else delete next[key];
|
|
3009
|
+
await db.update(aiConversations).set({ sessionDisabledTools: next }).where(eq(aiConversations.id, conversationId));
|
|
3010
|
+
}
|
|
3011
|
+
async function resetSessionDisabledTools(conversationId) {
|
|
3012
|
+
await db.update(aiConversations).set({ sessionDisabledTools: {} }).where(eq(aiConversations.id, conversationId));
|
|
3013
|
+
}
|
|
3014
|
+
async function createPendingAction(input) {
|
|
3015
|
+
const [row] = await db.insert(aiPendingActions).values({
|
|
3016
|
+
conversationId: input.conversationId,
|
|
3017
|
+
pauseId: input.pauseId,
|
|
3018
|
+
kind: input.kind ?? "permission",
|
|
3019
|
+
scenarioId: input.scenarioId,
|
|
3020
|
+
writeScenarioId: input.writeScenarioId ?? null,
|
|
3021
|
+
assistantBlocks: input.assistantBlocks,
|
|
3022
|
+
completedResults: input.completedResults,
|
|
3023
|
+
pending: input.pending
|
|
3024
|
+
}).returning();
|
|
3025
|
+
return row;
|
|
3026
|
+
}
|
|
3027
|
+
async function getActivePendingAction(conversationId) {
|
|
3028
|
+
const [row] = await db.select().from(aiPendingActions).where(
|
|
3029
|
+
and(
|
|
3030
|
+
eq(aiPendingActions.conversationId, conversationId),
|
|
3031
|
+
isNull(aiPendingActions.resolvedAt)
|
|
3032
|
+
)
|
|
3033
|
+
).limit(1);
|
|
3034
|
+
return row ?? null;
|
|
3035
|
+
}
|
|
3036
|
+
async function resolvePendingAction(id) {
|
|
3037
|
+
await db.update(aiPendingActions).set({ resolvedAt: /* @__PURE__ */ new Date() }).where(eq(aiPendingActions.id, id));
|
|
3038
|
+
}
|
|
3039
|
+
async function updatePendingActionTimeline(pauseId, timeline) {
|
|
3040
|
+
await db.update(aiPendingActions).set({ timeline }).where(and(eq(aiPendingActions.pauseId, pauseId), isNull(aiPendingActions.resolvedAt)));
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
// ../db/src/queries/local-user.ts
|
|
3044
|
+
var LOCAL_OWNER_EMAIL = "owner@localhost";
|
|
3045
|
+
var LOCAL_OWNER_ID = "00000000-0000-4000-a000-000000000000";
|
|
3046
|
+
var LOCAL_OWNER_COMPANY_ID = "00000000-0000-4000-a000-000000000001";
|
|
3047
|
+
async function createOwnerUserIfNone() {
|
|
3048
|
+
const [existing] = await db.select({ id: users.id }).from(users).limit(1);
|
|
3049
|
+
if (existing) return;
|
|
3050
|
+
await db.insert(users).values({
|
|
3051
|
+
id: LOCAL_OWNER_ID,
|
|
3052
|
+
email: LOCAL_OWNER_EMAIL,
|
|
3053
|
+
name: "Owner",
|
|
3054
|
+
emailVerified: /* @__PURE__ */ new Date(),
|
|
3055
|
+
passwordHash: null
|
|
3056
|
+
}).onConflictDoNothing({ target: users.email });
|
|
3057
|
+
}
|
|
3058
|
+
async function createOwnerCompanyIfNone(ownerId) {
|
|
3059
|
+
const [existing] = await db.select({ id: companyMembers.id }).from(companyMembers).limit(1);
|
|
3060
|
+
if (existing) return;
|
|
3061
|
+
await db.transaction(async (tx) => {
|
|
3062
|
+
await tx.insert(companies).values({
|
|
3063
|
+
id: LOCAL_OWNER_COMPANY_ID,
|
|
3064
|
+
name: "My Company",
|
|
3065
|
+
ownerId
|
|
3066
|
+
}).onConflictDoNothing({ target: companies.id });
|
|
3067
|
+
await tx.insert(companyMembers).values({
|
|
3068
|
+
companyId: LOCAL_OWNER_COMPANY_ID,
|
|
3069
|
+
userId: ownerId,
|
|
3070
|
+
role: "owner"
|
|
3071
|
+
}).onConflictDoNothing();
|
|
3072
|
+
});
|
|
3073
|
+
}
|
|
3074
|
+
async function getOwnerUser() {
|
|
3075
|
+
const [u] = await db.select({ id: users.id, email: users.email, name: users.name, image: users.image }).from(users).orderBy(asc(users.createdAt), asc(users.id)).limit(1);
|
|
3076
|
+
return u ?? null;
|
|
3077
|
+
}
|
|
3078
|
+
async function isOwnerClaimed() {
|
|
3079
|
+
const [u] = await db.select({ passwordHash: users.passwordHash }).from(users).orderBy(asc(users.createdAt), asc(users.id)).limit(1);
|
|
3080
|
+
return u != null && u.passwordHash != null;
|
|
3081
|
+
}
|
|
3082
|
+
async function listUsers() {
|
|
3083
|
+
const rows = await db.select({ id: users.id, email: users.email, name: users.name, passwordHash: users.passwordHash }).from(users).orderBy(asc(users.createdAt), asc(users.id));
|
|
3084
|
+
return rows.map((r) => ({ id: r.id, email: r.email, name: r.name, claimed: r.passwordHash != null }));
|
|
3085
|
+
}
|
|
3086
|
+
async function createUser(input) {
|
|
3087
|
+
const id = crypto.randomUUID();
|
|
3088
|
+
await db.insert(users).values({
|
|
3089
|
+
id,
|
|
3090
|
+
email: input.email,
|
|
3091
|
+
name: input.name ?? null,
|
|
3092
|
+
emailVerified: /* @__PURE__ */ new Date(),
|
|
3093
|
+
passwordHash: input.passwordHash
|
|
3094
|
+
});
|
|
3095
|
+
return { id };
|
|
3096
|
+
}
|
|
3097
|
+
async function setUserPassword(email, passwordHash) {
|
|
3098
|
+
const updated = await db.update(users).set({ passwordHash }).where(eq(users.email, email)).returning({ id: users.id });
|
|
3099
|
+
return updated.length;
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
// ../db/src/crypto.ts
|
|
3103
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
3104
|
+
var VERSION = "v1";
|
|
3105
|
+
var cachedKey;
|
|
3106
|
+
function getKey() {
|
|
3107
|
+
if (cachedKey === void 0) {
|
|
3108
|
+
const raw = process.env.SECRETS_ENCRYPTION_KEY;
|
|
3109
|
+
if (!raw) {
|
|
3110
|
+
cachedKey = null;
|
|
3111
|
+
} else {
|
|
3112
|
+
const buf = Buffer.from(raw, "base64");
|
|
3113
|
+
if (buf.length !== 32) {
|
|
3114
|
+
cachedKey = void 0;
|
|
3115
|
+
throw new Error("SECRETS_ENCRYPTION_KEY must be exactly 32 bytes, base64-encoded");
|
|
3116
|
+
}
|
|
3117
|
+
cachedKey = buf;
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
if (!cachedKey) {
|
|
3121
|
+
throw new Error(
|
|
3122
|
+
"SECRETS_ENCRYPTION_KEY is not set \u2014 required to store/read encrypted credentials. Generate one with: openssl rand -base64 32"
|
|
3123
|
+
);
|
|
3124
|
+
}
|
|
3125
|
+
return cachedKey;
|
|
3126
|
+
}
|
|
3127
|
+
function encryptSecret(plaintext) {
|
|
3128
|
+
const key = getKey();
|
|
3129
|
+
const iv = randomBytes(12);
|
|
3130
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
3131
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
3132
|
+
const tag = cipher.getAuthTag();
|
|
3133
|
+
return [VERSION, iv.toString("base64"), tag.toString("base64"), ciphertext.toString("base64")].join(":");
|
|
3134
|
+
}
|
|
3135
|
+
function decryptSecret(blob) {
|
|
3136
|
+
const key = getKey();
|
|
3137
|
+
const parts = blob.split(":");
|
|
3138
|
+
if (parts.length !== 4 || parts[0] !== VERSION || !parts[1] || !parts[2] || !parts[3]) {
|
|
3139
|
+
throw new Error("Malformed secret blob");
|
|
3140
|
+
}
|
|
3141
|
+
const tag = Buffer.from(parts[2], "base64");
|
|
3142
|
+
if (tag.length !== 16) {
|
|
3143
|
+
throw new Error("Malformed secret blob");
|
|
3144
|
+
}
|
|
3145
|
+
const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(parts[1], "base64"));
|
|
3146
|
+
decipher.setAuthTag(tag);
|
|
3147
|
+
return Buffer.concat([decipher.update(Buffer.from(parts[3], "base64")), decipher.final()]).toString("utf8");
|
|
3148
|
+
}
|
|
3149
|
+
function encryptJson(value) {
|
|
3150
|
+
return encryptSecret(JSON.stringify(value));
|
|
3151
|
+
}
|
|
3152
|
+
function decryptJson(blob) {
|
|
3153
|
+
return JSON.parse(decryptSecret(blob));
|
|
3154
|
+
}
|
|
3155
|
+
function __resetSecretsKeyCache() {
|
|
3156
|
+
cachedKey = void 0;
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
// ../db/src/queries/mcp.ts
|
|
3160
|
+
function slugifyConnectionName(name) {
|
|
3161
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3162
|
+
if (!slug)
|
|
3163
|
+
throw new Error(
|
|
3164
|
+
"Connection name must contain at least one alphanumeric character"
|
|
3165
|
+
);
|
|
3166
|
+
return slug;
|
|
3167
|
+
}
|
|
3168
|
+
function visibleWhere(companyId, userId) {
|
|
3169
|
+
return and(
|
|
3170
|
+
eq(mcpConnections.companyId, companyId),
|
|
3171
|
+
or(
|
|
3172
|
+
eq(mcpConnections.ownerScope, "company"),
|
|
3173
|
+
eq(mcpConnections.ownerUserId, userId)
|
|
3174
|
+
)
|
|
3175
|
+
);
|
|
3176
|
+
}
|
|
3177
|
+
async function listVisibleConnections(companyId, userId) {
|
|
3178
|
+
return db.select().from(mcpConnections).where(visibleWhere(companyId, userId));
|
|
3179
|
+
}
|
|
3180
|
+
async function getVisibleConnection(id, companyId, userId) {
|
|
3181
|
+
const rows = await db.select().from(mcpConnections).where(and(eq(mcpConnections.id, id), visibleWhere(companyId, userId))).limit(1);
|
|
3182
|
+
return rows[0] ?? null;
|
|
3183
|
+
}
|
|
3184
|
+
async function createMcpConnection(data) {
|
|
3185
|
+
if (data.ownerScope === "personal" && !data.ownerUserId) {
|
|
3186
|
+
throw new Error("Personal connections require ownerUserId");
|
|
3187
|
+
}
|
|
3188
|
+
const [row] = await db.insert(mcpConnections).values({ ...data, slug: slugifyConnectionName(data.name) }).returning();
|
|
3189
|
+
return row;
|
|
3190
|
+
}
|
|
3191
|
+
async function updateMcpConnection(id, companyId, patch) {
|
|
3192
|
+
const values = { ...patch };
|
|
3193
|
+
if (patch.name) values.slug = slugifyConnectionName(patch.name);
|
|
3194
|
+
const rows = await db.update(mcpConnections).set(values).where(
|
|
3195
|
+
and(
|
|
3196
|
+
eq(mcpConnections.id, id),
|
|
3197
|
+
eq(mcpConnections.companyId, companyId)
|
|
3198
|
+
)
|
|
3199
|
+
).returning();
|
|
3200
|
+
return rows[0] ?? null;
|
|
3201
|
+
}
|
|
3202
|
+
async function deleteMcpConnection(id, companyId) {
|
|
3203
|
+
const rows = await db.delete(mcpConnections).where(
|
|
3204
|
+
and(
|
|
3205
|
+
eq(mcpConnections.id, id),
|
|
3206
|
+
eq(mcpConnections.companyId, companyId)
|
|
3207
|
+
)
|
|
3208
|
+
).returning({ id: mcpConnections.id });
|
|
3209
|
+
return rows.length > 0;
|
|
3210
|
+
}
|
|
3211
|
+
async function saveMcpCredentials(mcpConnectionId, authType, secret, clientRegistration) {
|
|
3212
|
+
const encrypted = secret === null ? null : encryptJson(secret);
|
|
3213
|
+
const expiresAt = secret && "expiresAt" in secret && secret.expiresAt ? new Date(secret.expiresAt) : null;
|
|
3214
|
+
await db.insert(mcpCredentials).values({
|
|
3215
|
+
mcpConnectionId,
|
|
3216
|
+
authType,
|
|
3217
|
+
secret: encrypted,
|
|
3218
|
+
clientRegistration,
|
|
3219
|
+
expiresAt
|
|
3220
|
+
}).onConflictDoUpdate({
|
|
3221
|
+
target: mcpCredentials.mcpConnectionId,
|
|
3222
|
+
set: {
|
|
3223
|
+
authType,
|
|
3224
|
+
...secret !== null ? { secret: encrypted, expiresAt } : {},
|
|
3225
|
+
...clientRegistration !== void 0 ? { clientRegistration } : {}
|
|
3226
|
+
}
|
|
3227
|
+
});
|
|
3228
|
+
}
|
|
3229
|
+
async function getMcpCredentialsRow(mcpConnectionId) {
|
|
3230
|
+
const rows = await db.select().from(mcpCredentials).where(eq(mcpCredentials.mcpConnectionId, mcpConnectionId)).limit(1);
|
|
3231
|
+
return rows[0] ?? null;
|
|
3232
|
+
}
|
|
3233
|
+
async function getDecryptedMcpSecret(mcpConnectionId) {
|
|
3234
|
+
const row = await getMcpCredentialsRow(mcpConnectionId);
|
|
3235
|
+
if (!row?.secret) return null;
|
|
3236
|
+
return decryptJson(row.secret);
|
|
3237
|
+
}
|
|
3238
|
+
async function listMcpToolPrefs(mcpConnectionId) {
|
|
3239
|
+
return db.select().from(mcpToolPrefs).where(eq(mcpToolPrefs.mcpConnectionId, mcpConnectionId));
|
|
3240
|
+
}
|
|
3241
|
+
async function upsertMcpToolPref(mcpConnectionId, toolName, patch) {
|
|
3242
|
+
await db.insert(mcpToolPrefs).values({
|
|
3243
|
+
mcpConnectionId,
|
|
3244
|
+
toolName,
|
|
3245
|
+
enabled: patch.enabled ?? true,
|
|
3246
|
+
permClassOverride: patch.permClassOverride ?? null
|
|
3247
|
+
}).onConflictDoUpdate({
|
|
3248
|
+
target: [mcpToolPrefs.mcpConnectionId, mcpToolPrefs.toolName],
|
|
3249
|
+
set: {
|
|
3250
|
+
...patch.enabled !== void 0 ? { enabled: patch.enabled } : {},
|
|
3251
|
+
...patch.permClassOverride !== void 0 ? { permClassOverride: patch.permClassOverride } : {}
|
|
3252
|
+
}
|
|
3253
|
+
});
|
|
3254
|
+
}
|
|
3255
|
+
async function getDisabledMcpConnectionIds(userId, companyId) {
|
|
3256
|
+
const rows = await db.select({ disabled: userPreferences.disabledMcpConnections }).from(userPreferences).where(
|
|
3257
|
+
and(
|
|
3258
|
+
eq(userPreferences.userId, userId),
|
|
3259
|
+
eq(userPreferences.companyId, companyId)
|
|
3260
|
+
)
|
|
3261
|
+
).limit(1);
|
|
3262
|
+
return rows[0]?.disabled ?? [];
|
|
3263
|
+
}
|
|
3264
|
+
async function getDisabledBuiltinTools(userId, companyId) {
|
|
3265
|
+
const rows = await db.select({ disabled: userPreferences.disabledBuiltinTools }).from(userPreferences).where(
|
|
3266
|
+
and(
|
|
3267
|
+
eq(userPreferences.userId, userId),
|
|
3268
|
+
eq(userPreferences.companyId, companyId)
|
|
3269
|
+
)
|
|
3270
|
+
).limit(1);
|
|
3271
|
+
return rows[0]?.disabled ?? [];
|
|
3272
|
+
}
|
|
3273
|
+
async function upsertUserPreferences(userId, companyId, patch) {
|
|
3274
|
+
const [row] = await db.insert(userPreferences).values({ userId, companyId, ...patch }).onConflictDoUpdate({
|
|
3275
|
+
target: [userPreferences.userId, userPreferences.companyId],
|
|
3276
|
+
set: patch
|
|
3277
|
+
}).returning();
|
|
3278
|
+
return row;
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
// ../db/src/queries/ai-providers.ts
|
|
3282
|
+
function toPublic(row) {
|
|
3283
|
+
const { apiKeyEncrypted, ...rest } = row;
|
|
3284
|
+
return { ...rest, apiKeySet: apiKeyEncrypted != null };
|
|
3285
|
+
}
|
|
3286
|
+
async function withModelSummary(row) {
|
|
3287
|
+
const models = await listAiProviderModels(row.id);
|
|
3288
|
+
const defaultModelId = models.find((m) => m.isDefault)?.modelId ?? null;
|
|
3289
|
+
return { ...toPublic(row), modelCount: models.length, defaultModelId };
|
|
3290
|
+
}
|
|
3291
|
+
async function listAiProviders(companyId) {
|
|
3292
|
+
const rows = await db.select().from(aiProviders).where(eq(aiProviders.companyId, companyId));
|
|
3293
|
+
return Promise.all(rows.map(withModelSummary));
|
|
3294
|
+
}
|
|
3295
|
+
async function getAiProvider(id, companyId) {
|
|
3296
|
+
const [row] = await db.select().from(aiProviders).where(and(eq(aiProviders.id, id), eq(aiProviders.companyId, companyId))).limit(1);
|
|
3297
|
+
return row ? withModelSummary(row) : null;
|
|
3298
|
+
}
|
|
3299
|
+
async function createAiProvider(data) {
|
|
3300
|
+
const existing = await db.select({ id: aiProviders.id }).from(aiProviders).where(eq(aiProviders.companyId, data.companyId)).limit(1);
|
|
3301
|
+
const isDefault = existing.length === 0;
|
|
3302
|
+
const [row] = await db.insert(aiProviders).values({
|
|
3303
|
+
companyId: data.companyId,
|
|
3304
|
+
name: data.name,
|
|
3305
|
+
kind: data.kind,
|
|
3306
|
+
baseUrl: data.baseUrl ?? null,
|
|
3307
|
+
apiKeyEncrypted: data.apiKey ? encryptSecret(data.apiKey) : null,
|
|
3308
|
+
apiKeyMode: data.apiKeyMode ?? "user_provided",
|
|
3309
|
+
headers: data.headers ?? null,
|
|
3310
|
+
dropParams: data.dropParams ?? null,
|
|
3311
|
+
isDefault
|
|
3312
|
+
}).returning();
|
|
3313
|
+
return withModelSummary(row);
|
|
3314
|
+
}
|
|
3315
|
+
async function updateAiProvider(id, companyId, patch) {
|
|
3316
|
+
const values = {};
|
|
3317
|
+
if (patch.name !== void 0) values.name = patch.name;
|
|
3318
|
+
if (patch.baseUrl !== void 0) values.baseUrl = patch.baseUrl;
|
|
3319
|
+
if (patch.apiKeyMode !== void 0) values.apiKeyMode = patch.apiKeyMode;
|
|
3320
|
+
if (patch.headers !== void 0) values.headers = patch.headers;
|
|
3321
|
+
if (patch.dropParams !== void 0) values.dropParams = patch.dropParams;
|
|
3322
|
+
if (patch.enabled !== void 0) values.enabled = patch.enabled;
|
|
3323
|
+
if (patch.apiKey !== void 0) values.apiKeyEncrypted = patch.apiKey ? encryptSecret(patch.apiKey) : null;
|
|
3324
|
+
if (Object.keys(values).length === 0) return getAiProvider(id, companyId);
|
|
3325
|
+
const [row] = await db.update(aiProviders).set(values).where(and(eq(aiProviders.id, id), eq(aiProviders.companyId, companyId))).returning();
|
|
3326
|
+
return row ? withModelSummary(row) : null;
|
|
3327
|
+
}
|
|
3328
|
+
async function deleteAiProvider(id, companyId) {
|
|
3329
|
+
const rows = await db.delete(aiProviders).where(and(eq(aiProviders.id, id), eq(aiProviders.companyId, companyId))).returning({ id: aiProviders.id });
|
|
3330
|
+
return rows.length > 0;
|
|
3331
|
+
}
|
|
3332
|
+
async function setDefaultAiProvider(id, companyId) {
|
|
3333
|
+
const target = await getAiProvider(id, companyId);
|
|
3334
|
+
if (!target) return false;
|
|
3335
|
+
await db.transaction(async (tx) => {
|
|
3336
|
+
await tx.update(aiProviders).set({ isDefault: false }).where(eq(aiProviders.companyId, companyId));
|
|
3337
|
+
await tx.update(aiProviders).set({ isDefault: true }).where(and(eq(aiProviders.id, id), eq(aiProviders.companyId, companyId)));
|
|
3338
|
+
});
|
|
3339
|
+
return true;
|
|
3340
|
+
}
|
|
3341
|
+
async function getDefaultAiProvider(companyId) {
|
|
3342
|
+
const [row] = await db.select().from(aiProviders).where(and(eq(aiProviders.companyId, companyId), eq(aiProviders.isDefault, true), eq(aiProviders.enabled, true))).limit(1);
|
|
3343
|
+
return row ?? null;
|
|
3344
|
+
}
|
|
3345
|
+
async function getDecryptedProviderKey(id, companyId) {
|
|
3346
|
+
const [row] = await db.select({ enc: aiProviders.apiKeyEncrypted }).from(aiProviders).where(and(eq(aiProviders.id, id), eq(aiProviders.companyId, companyId))).limit(1);
|
|
3347
|
+
if (!row?.enc) return null;
|
|
3348
|
+
return decryptSecret(row.enc);
|
|
3349
|
+
}
|
|
3350
|
+
async function listAiProviderModels(providerId) {
|
|
3351
|
+
return db.select().from(aiProviderModels).where(eq(aiProviderModels.providerId, providerId));
|
|
3352
|
+
}
|
|
3353
|
+
async function addAiProviderModel(providerId, data) {
|
|
3354
|
+
const [row] = await db.insert(aiProviderModels).values({
|
|
3355
|
+
providerId,
|
|
3356
|
+
modelId: data.modelId,
|
|
3357
|
+
displayName: data.displayName ?? null,
|
|
3358
|
+
contextWindow: data.contextWindow ?? null,
|
|
3359
|
+
maxOutputTokens: data.maxOutputTokens ?? null,
|
|
3360
|
+
supportsTools: data.supportsTools ?? null,
|
|
3361
|
+
supportsImages: data.supportsImages ?? null,
|
|
3362
|
+
source: data.source,
|
|
3363
|
+
enabled: data.enabled ?? true
|
|
3364
|
+
}).onConflictDoUpdate({
|
|
3365
|
+
target: [aiProviderModels.providerId, aiProviderModels.modelId],
|
|
3366
|
+
set: {
|
|
3367
|
+
displayName: data.displayName ?? null,
|
|
3368
|
+
contextWindow: data.contextWindow ?? null,
|
|
3369
|
+
maxOutputTokens: data.maxOutputTokens ?? null,
|
|
3370
|
+
supportsTools: data.supportsTools ?? null,
|
|
3371
|
+
supportsImages: data.supportsImages ?? null
|
|
3372
|
+
}
|
|
3373
|
+
}).returning();
|
|
3374
|
+
return row;
|
|
3375
|
+
}
|
|
3376
|
+
async function setDefaultAiProviderModel(modelId, providerId) {
|
|
3377
|
+
const [target] = await db.select({ id: aiProviderModels.id }).from(aiProviderModels).where(and(eq(aiProviderModels.id, modelId), eq(aiProviderModels.providerId, providerId))).limit(1);
|
|
3378
|
+
if (!target) return false;
|
|
3379
|
+
await db.transaction(async (tx) => {
|
|
3380
|
+
await tx.update(aiProviderModels).set({ isDefault: false }).where(eq(aiProviderModels.providerId, providerId));
|
|
3381
|
+
await tx.update(aiProviderModels).set({ isDefault: true }).where(and(eq(aiProviderModels.id, modelId), eq(aiProviderModels.providerId, providerId)));
|
|
3382
|
+
});
|
|
3383
|
+
return true;
|
|
3384
|
+
}
|
|
3385
|
+
async function getResolvedDefaultModelId(providerId) {
|
|
3386
|
+
const [row] = await db.select({ modelId: aiProviderModels.modelId }).from(aiProviderModels).where(and(eq(aiProviderModels.providerId, providerId), eq(aiProviderModels.isDefault, true), eq(aiProviderModels.enabled, true))).limit(1);
|
|
3387
|
+
return row?.modelId ?? null;
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
// ../db/src/token-hash.ts
|
|
3391
|
+
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
3392
|
+
function sha256hex(value) {
|
|
3393
|
+
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
3394
|
+
}
|
|
3395
|
+
function generateSecretToken(prefix) {
|
|
3396
|
+
const secret = randomBytes2(32).toString("base64url");
|
|
3397
|
+
const token = `${prefix}${secret}`;
|
|
3398
|
+
return { token, hash: sha256hex(token), lastFour: secret.slice(-4) };
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
// ../db/src/queries/api-tokens.ts
|
|
3402
|
+
function roleScopeCap(role) {
|
|
3403
|
+
if (role === "viewer") return ["read"];
|
|
3404
|
+
if (role === "editor" || role === "admin" || role === "owner") {
|
|
3405
|
+
return ["read", "write", "delete"];
|
|
3406
|
+
}
|
|
3407
|
+
return [];
|
|
3408
|
+
}
|
|
3409
|
+
async function mintApiToken(input) {
|
|
3410
|
+
const generated = generateSecretToken("bl_pat_");
|
|
3411
|
+
const [row] = await db.insert(apiTokens).values({
|
|
3412
|
+
userId: input.userId,
|
|
3413
|
+
companyId: input.companyId,
|
|
3414
|
+
name: input.name,
|
|
3415
|
+
tokenHash: generated.hash,
|
|
3416
|
+
scopes: input.scopes,
|
|
3417
|
+
lastFour: generated.lastFour,
|
|
3418
|
+
expiresAt: input.expiresAt ?? null
|
|
3419
|
+
}).returning();
|
|
3420
|
+
return { row, plaintext: generated.token };
|
|
3421
|
+
}
|
|
3422
|
+
async function listApiTokensForUser(companyId, userId) {
|
|
3423
|
+
return db.select().from(apiTokens).where(
|
|
3424
|
+
and(
|
|
3425
|
+
eq(apiTokens.companyId, companyId),
|
|
3426
|
+
eq(apiTokens.userId, userId),
|
|
3427
|
+
isNull(apiTokens.revokedAt)
|
|
3428
|
+
)
|
|
3429
|
+
);
|
|
3430
|
+
}
|
|
3431
|
+
async function revokeApiToken(id, companyId, userId) {
|
|
3432
|
+
const rows = await db.update(apiTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(
|
|
3433
|
+
and(
|
|
3434
|
+
eq(apiTokens.id, id),
|
|
3435
|
+
eq(apiTokens.companyId, companyId),
|
|
3436
|
+
eq(apiTokens.userId, userId),
|
|
3437
|
+
isNull(apiTokens.revokedAt)
|
|
3438
|
+
)
|
|
3439
|
+
).returning({ id: apiTokens.id });
|
|
3440
|
+
return rows.length > 0;
|
|
3441
|
+
}
|
|
3442
|
+
async function findApiTokenByHash(tokenHash) {
|
|
3443
|
+
const rows = await db.select().from(apiTokens).where(eq(apiTokens.tokenHash, tokenHash)).limit(1);
|
|
3444
|
+
return rows[0] ?? null;
|
|
3445
|
+
}
|
|
3446
|
+
async function touchApiTokenLastUsed(id) {
|
|
3447
|
+
const cutoff = new Date(Date.now() - 6e4);
|
|
3448
|
+
await db.update(apiTokens).set({ lastUsedAt: /* @__PURE__ */ new Date() }).where(
|
|
3449
|
+
and(
|
|
3450
|
+
eq(apiTokens.id, id),
|
|
3451
|
+
or(isNull(apiTokens.lastUsedAt), lt(apiTokens.lastUsedAt, cutoff))
|
|
3452
|
+
)
|
|
3453
|
+
);
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
// ../db/src/queries/notifications.ts
|
|
3457
|
+
async function createNotification(input) {
|
|
3458
|
+
const [row] = await db.insert(notifications).values({
|
|
3459
|
+
companyId: input.companyId,
|
|
3460
|
+
userId: input.userId,
|
|
3461
|
+
category: input.category,
|
|
3462
|
+
title: input.title,
|
|
3463
|
+
body: input.body ?? null,
|
|
3464
|
+
severity: input.severity ?? "info",
|
|
3465
|
+
link: input.link ?? null,
|
|
3466
|
+
metadata: input.metadata ?? null
|
|
3467
|
+
}).returning();
|
|
3468
|
+
return row;
|
|
3469
|
+
}
|
|
3470
|
+
async function listNotificationsForUser(userId, companyId, limit = 50) {
|
|
3471
|
+
return db.select().from(notifications).where(and(eq(notifications.userId, userId), eq(notifications.companyId, companyId))).orderBy(desc(notifications.createdAt)).limit(limit);
|
|
3472
|
+
}
|
|
3473
|
+
async function countUnreadNotifications(userId, companyId) {
|
|
3474
|
+
const [row] = await db.select({ count: sql`count(*)::int` }).from(notifications).where(
|
|
3475
|
+
and(
|
|
3476
|
+
eq(notifications.userId, userId),
|
|
3477
|
+
eq(notifications.companyId, companyId),
|
|
3478
|
+
isNull(notifications.readAt)
|
|
3479
|
+
)
|
|
3480
|
+
);
|
|
3481
|
+
return row?.count ?? 0;
|
|
3482
|
+
}
|
|
3483
|
+
async function markNotificationsRead(userId, companyId, opts) {
|
|
3484
|
+
const scope = and(eq(notifications.userId, userId), eq(notifications.companyId, companyId));
|
|
3485
|
+
if (opts.all) {
|
|
3486
|
+
await db.update(notifications).set({ readAt: /* @__PURE__ */ new Date() }).where(and(scope, isNull(notifications.readAt)));
|
|
3487
|
+
return;
|
|
3488
|
+
}
|
|
3489
|
+
if (opts.ids && opts.ids.length > 0) {
|
|
3490
|
+
await db.update(notifications).set({ readAt: /* @__PURE__ */ new Date() }).where(and(scope, inArray(notifications.id, opts.ids)));
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3494
|
+
// ../db/src/queries/oauth.ts
|
|
3495
|
+
var AUTH_CODE_TTL_MS = 10 * 60 * 1e3;
|
|
3496
|
+
var ACCESS_TOKEN_TTL_MS = 60 * 60 * 1e3;
|
|
3497
|
+
async function createOauthClient(input) {
|
|
3498
|
+
const [row] = await db.insert(oauthClients).values({ name: input.name, redirectUris: input.redirectUris }).returning();
|
|
3499
|
+
return row;
|
|
3500
|
+
}
|
|
3501
|
+
async function getOauthClientById(clientId) {
|
|
3502
|
+
const rows = await db.select().from(oauthClients).where(eq(oauthClients.id, clientId)).limit(1);
|
|
3503
|
+
return rows[0] ?? null;
|
|
3504
|
+
}
|
|
3505
|
+
async function createAuthCode(input) {
|
|
3506
|
+
const generated = generateSecretToken("bl_ac_");
|
|
3507
|
+
await db.insert(oauthAuthCodes).values({
|
|
3508
|
+
codeHash: generated.hash,
|
|
3509
|
+
clientId: input.clientId,
|
|
3510
|
+
userId: input.userId,
|
|
3511
|
+
companyId: input.companyId,
|
|
3512
|
+
scopes: input.scopes,
|
|
3513
|
+
codeChallenge: input.codeChallenge,
|
|
3514
|
+
resource: input.resource,
|
|
3515
|
+
redirectUri: input.redirectUri,
|
|
3516
|
+
expiresAt: new Date(Date.now() + AUTH_CODE_TTL_MS)
|
|
3517
|
+
});
|
|
3518
|
+
return { code: generated.token };
|
|
3519
|
+
}
|
|
3520
|
+
async function consumeAuthCode(code) {
|
|
3521
|
+
const rows = await db.update(oauthAuthCodes).set({ usedAt: /* @__PURE__ */ new Date() }).where(
|
|
3522
|
+
and(
|
|
3523
|
+
eq(oauthAuthCodes.codeHash, sha256hex(code)),
|
|
3524
|
+
isNull(oauthAuthCodes.usedAt),
|
|
3525
|
+
gt(oauthAuthCodes.expiresAt, /* @__PURE__ */ new Date())
|
|
3526
|
+
)
|
|
3527
|
+
).returning();
|
|
3528
|
+
return rows[0] ?? null;
|
|
3529
|
+
}
|
|
3530
|
+
async function issueOauthTokens(input) {
|
|
3531
|
+
const access = generateSecretToken("bl_at_");
|
|
3532
|
+
const refresh = generateSecretToken("bl_rt_");
|
|
3533
|
+
const [row] = await db.insert(oauthTokens).values({
|
|
3534
|
+
grantId: input.grantId ?? crypto.randomUUID(),
|
|
3535
|
+
clientId: input.clientId,
|
|
3536
|
+
userId: input.userId,
|
|
3537
|
+
companyId: input.companyId,
|
|
3538
|
+
scopes: input.scopes,
|
|
3539
|
+
accessTokenHash: access.hash,
|
|
3540
|
+
refreshTokenHash: refresh.hash,
|
|
3541
|
+
resource: input.resource,
|
|
3542
|
+
accessExpiresAt: new Date(Date.now() + ACCESS_TOKEN_TTL_MS)
|
|
3543
|
+
}).returning();
|
|
3544
|
+
return { accessToken: access.token, refreshToken: refresh.token, row };
|
|
3545
|
+
}
|
|
3546
|
+
async function findOauthTokenByAccessHash(accessTokenHash) {
|
|
3547
|
+
const rows = await db.select().from(oauthTokens).where(eq(oauthTokens.accessTokenHash, accessTokenHash)).limit(1);
|
|
3548
|
+
return rows[0] ?? null;
|
|
3549
|
+
}
|
|
3550
|
+
async function rotateRefreshToken(refreshToken, clientId) {
|
|
3551
|
+
const hash = sha256hex(refreshToken);
|
|
3552
|
+
const result = await db.transaction(async (tx) => {
|
|
3553
|
+
const claimed = await tx.update(oauthTokens).set({ supersededAt: /* @__PURE__ */ new Date() }).where(
|
|
3554
|
+
and(
|
|
3555
|
+
eq(oauthTokens.refreshTokenHash, hash),
|
|
3556
|
+
eq(oauthTokens.clientId, clientId),
|
|
3557
|
+
isNull(oauthTokens.supersededAt),
|
|
3558
|
+
isNull(oauthTokens.revokedAt)
|
|
3559
|
+
)
|
|
3560
|
+
).returning();
|
|
3561
|
+
if (claimed.length === 0) {
|
|
3562
|
+
return null;
|
|
3563
|
+
}
|
|
3564
|
+
const claimedRow = claimed[0];
|
|
3565
|
+
const access = generateSecretToken("bl_at_");
|
|
3566
|
+
const refresh = generateSecretToken("bl_rt_");
|
|
3567
|
+
const [newRow] = await tx.insert(oauthTokens).values({
|
|
3568
|
+
grantId: claimedRow.grantId,
|
|
3569
|
+
clientId: claimedRow.clientId,
|
|
3570
|
+
userId: claimedRow.userId,
|
|
3571
|
+
companyId: claimedRow.companyId,
|
|
3572
|
+
scopes: claimedRow.scopes,
|
|
3573
|
+
accessTokenHash: access.hash,
|
|
3574
|
+
refreshTokenHash: refresh.hash,
|
|
3575
|
+
resource: claimedRow.resource,
|
|
3576
|
+
accessExpiresAt: new Date(Date.now() + ACCESS_TOKEN_TTL_MS)
|
|
3577
|
+
}).returning();
|
|
3578
|
+
return {
|
|
3579
|
+
accessToken: access.token,
|
|
3580
|
+
refreshToken: refresh.token,
|
|
3581
|
+
row: newRow
|
|
3582
|
+
};
|
|
3583
|
+
});
|
|
3584
|
+
if (result !== null) {
|
|
3585
|
+
return { status: "rotated", ...result };
|
|
3586
|
+
}
|
|
3587
|
+
const existing = await db.select().from(oauthTokens).where(eq(oauthTokens.refreshTokenHash, hash)).limit(1);
|
|
3588
|
+
const row = existing[0];
|
|
3589
|
+
if (!row || row.revokedAt) return { status: "invalid" };
|
|
3590
|
+
if (!row.supersededAt) return { status: "invalid" };
|
|
3591
|
+
await db.update(oauthTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(and(eq(oauthTokens.grantId, row.grantId), isNull(oauthTokens.revokedAt)));
|
|
3592
|
+
return { status: "reuse_detected" };
|
|
3593
|
+
}
|
|
3594
|
+
async function listOauthGrantsForUser(companyId, userId) {
|
|
3595
|
+
return db.select({
|
|
3596
|
+
grantId: oauthTokens.grantId,
|
|
3597
|
+
clientId: oauthTokens.clientId,
|
|
3598
|
+
clientName: oauthClients.name,
|
|
3599
|
+
scopes: oauthTokens.scopes,
|
|
3600
|
+
createdAt: oauthTokens.createdAt
|
|
3601
|
+
}).from(oauthTokens).innerJoin(oauthClients, eq(oauthTokens.clientId, oauthClients.id)).where(
|
|
3602
|
+
and(
|
|
3603
|
+
eq(oauthTokens.companyId, companyId),
|
|
3604
|
+
eq(oauthTokens.userId, userId),
|
|
3605
|
+
isNull(oauthTokens.revokedAt),
|
|
3606
|
+
isNull(oauthTokens.supersededAt)
|
|
3607
|
+
)
|
|
3608
|
+
);
|
|
3609
|
+
}
|
|
3610
|
+
async function revokeOauthGrant(grantId, companyId, userId) {
|
|
3611
|
+
const rows = await db.update(oauthTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(
|
|
3612
|
+
and(
|
|
3613
|
+
eq(oauthTokens.grantId, grantId),
|
|
3614
|
+
eq(oauthTokens.companyId, companyId),
|
|
3615
|
+
eq(oauthTokens.userId, userId),
|
|
3616
|
+
isNull(oauthTokens.revokedAt)
|
|
3617
|
+
)
|
|
3618
|
+
).returning({ id: oauthTokens.id });
|
|
3619
|
+
return rows.length > 0;
|
|
3620
|
+
}
|
|
3621
|
+
|
|
3622
|
+
// ../db/src/queries/scheduled-jobs.ts
|
|
3623
|
+
async function createScheduledJob(input) {
|
|
3624
|
+
const [row] = await db.insert(scheduledJobs).values({
|
|
3625
|
+
companyId: input.companyId,
|
|
3626
|
+
createdByUserId: input.createdByUserId,
|
|
3627
|
+
name: input.name,
|
|
3628
|
+
prompt: input.prompt,
|
|
3629
|
+
actionKind: input.actionKind,
|
|
3630
|
+
allowedTools: input.allowedTools,
|
|
3631
|
+
boundConnectionIds: input.boundConnectionIds,
|
|
3632
|
+
schedule: input.schedule,
|
|
3633
|
+
timezone: input.timezone ?? "UTC",
|
|
3634
|
+
notifyPolicy: input.notifyPolicy ?? "smart",
|
|
3635
|
+
nextRunAt: input.nextRunAt
|
|
3636
|
+
}).returning();
|
|
3637
|
+
if (!row) throw new Error("createScheduledJob: insert returned no row");
|
|
3638
|
+
return row;
|
|
3639
|
+
}
|
|
3640
|
+
async function getScheduledJob(id, companyId) {
|
|
3641
|
+
const [row] = await db.select().from(scheduledJobs).where(and(eq(scheduledJobs.id, id), eq(scheduledJobs.companyId, companyId), isNull(scheduledJobs.deletedAt)));
|
|
3642
|
+
return row ?? null;
|
|
3643
|
+
}
|
|
3644
|
+
async function getScheduledJobById(id) {
|
|
3645
|
+
const [row] = await db.select().from(scheduledJobs).where(and(eq(scheduledJobs.id, id), isNull(scheduledJobs.deletedAt)));
|
|
3646
|
+
return row ?? null;
|
|
3647
|
+
}
|
|
3648
|
+
async function listScheduledJobs(companyId) {
|
|
3649
|
+
return db.select().from(scheduledJobs).where(and(eq(scheduledJobs.companyId, companyId), isNull(scheduledJobs.deletedAt))).orderBy(desc(scheduledJobs.createdAt));
|
|
3650
|
+
}
|
|
3651
|
+
async function countScheduledJobs(companyId) {
|
|
3652
|
+
const [row] = await db.select({ count: sql`count(*)::int` }).from(scheduledJobs).where(and(eq(scheduledJobs.companyId, companyId), isNull(scheduledJobs.deletedAt)));
|
|
3653
|
+
return row?.count ?? 0;
|
|
3654
|
+
}
|
|
3655
|
+
async function updateScheduledJob(id, companyId, patch) {
|
|
3656
|
+
const [row] = await db.update(scheduledJobs).set(patch).where(and(eq(scheduledJobs.id, id), eq(scheduledJobs.companyId, companyId), isNull(scheduledJobs.deletedAt))).returning();
|
|
3657
|
+
return row ?? null;
|
|
3658
|
+
}
|
|
3659
|
+
async function softDeleteScheduledJob(id, companyId) {
|
|
3660
|
+
await db.update(scheduledJobs).set({ deletedAt: /* @__PURE__ */ new Date(), enabled: false }).where(and(eq(scheduledJobs.id, id), eq(scheduledJobs.companyId, companyId)));
|
|
3661
|
+
}
|
|
3662
|
+
async function listDueScheduledJobs(now) {
|
|
3663
|
+
return db.select().from(scheduledJobs).where(
|
|
3664
|
+
and(
|
|
3665
|
+
eq(scheduledJobs.enabled, true),
|
|
3666
|
+
eq(scheduledJobs.status, "active"),
|
|
3667
|
+
isNull(scheduledJobs.deletedAt),
|
|
3668
|
+
lte(scheduledJobs.nextRunAt, now)
|
|
3669
|
+
)
|
|
3670
|
+
).orderBy(asc(scheduledJobs.nextRunAt));
|
|
3671
|
+
}
|
|
3672
|
+
async function startScheduledJobRun(input) {
|
|
3673
|
+
const [row] = await db.insert(scheduledJobRuns).values({ scheduledJobId: input.scheduledJobId, companyId: input.companyId, status: "running", trigger: input.trigger, startedAt: /* @__PURE__ */ new Date() }).returning();
|
|
3674
|
+
if (!row) throw new Error("startScheduledJobRun: insert returned no row");
|
|
3675
|
+
return row;
|
|
3676
|
+
}
|
|
3677
|
+
async function finishScheduledJobRun(runId, companyId, result) {
|
|
3678
|
+
const scope = and(eq(scheduledJobRuns.id, runId), eq(scheduledJobRuns.companyId, companyId));
|
|
3679
|
+
const [existing] = await db.select({ startedAt: scheduledJobRuns.startedAt }).from(scheduledJobRuns).where(scope);
|
|
3680
|
+
const finishedAt = /* @__PURE__ */ new Date();
|
|
3681
|
+
const durationMs = existing ? Math.max(0, finishedAt.getTime() - existing.startedAt.getTime()) : null;
|
|
3682
|
+
const [row] = await db.update(scheduledJobRuns).set({
|
|
3683
|
+
status: result.status,
|
|
3684
|
+
summary: result.summary ?? null,
|
|
3685
|
+
tokensUsed: result.tokensUsed ?? null,
|
|
3686
|
+
output: result.output ?? null,
|
|
3687
|
+
error: result.error ?? null,
|
|
3688
|
+
finishedAt,
|
|
3689
|
+
durationMs
|
|
3690
|
+
}).where(scope).returning();
|
|
3691
|
+
if (!row) throw new Error("finishScheduledJobRun: update returned no row");
|
|
3692
|
+
return row;
|
|
3693
|
+
}
|
|
3694
|
+
async function recordMissedRun(scheduledJobId, companyId, summary) {
|
|
3695
|
+
const now = /* @__PURE__ */ new Date();
|
|
3696
|
+
const [row] = await db.insert(scheduledJobRuns).values({
|
|
3697
|
+
scheduledJobId,
|
|
3698
|
+
companyId,
|
|
3699
|
+
status: "missed",
|
|
3700
|
+
trigger: "schedule",
|
|
3701
|
+
startedAt: now,
|
|
3702
|
+
finishedAt: now,
|
|
3703
|
+
durationMs: 0,
|
|
3704
|
+
summary
|
|
3705
|
+
}).returning();
|
|
3706
|
+
if (!row) throw new Error("recordMissedRun: insert returned no row");
|
|
3707
|
+
return row;
|
|
3708
|
+
}
|
|
3709
|
+
async function listScheduledJobRuns(scheduledJobId, companyId, limit = 50) {
|
|
3710
|
+
return db.select().from(scheduledJobRuns).where(and(eq(scheduledJobRuns.scheduledJobId, scheduledJobId), eq(scheduledJobRuns.companyId, companyId))).orderBy(desc(scheduledJobRuns.startedAt)).limit(limit);
|
|
3711
|
+
}
|
|
3712
|
+
|
|
3713
|
+
// ../db/src/index.ts
|
|
3714
|
+
var g = globalThis;
|
|
3715
|
+
function lazySyncPostgresOrThrow() {
|
|
3716
|
+
const resolved = resolveDriver(process.env);
|
|
3717
|
+
if (resolved.driver !== "postgres") {
|
|
3718
|
+
throw new Error(
|
|
3719
|
+
"Database not initialized \u2014 initDatabase() must run first (instrumentation.register does this on boot). PGLite cannot be created synchronously."
|
|
3720
|
+
);
|
|
3721
|
+
}
|
|
3722
|
+
const client = src_default(resolved.connectionString, { max: 10 });
|
|
3723
|
+
const db2 = drizzle(client, { schema: schema_exports });
|
|
3724
|
+
g.__burnless_db = db2;
|
|
3725
|
+
g.__burnless_handle = { db: db2, dialect: "postgres", raw: client, close: () => client.end() };
|
|
3726
|
+
return db2;
|
|
3727
|
+
}
|
|
3728
|
+
function resolveLiveDb() {
|
|
3729
|
+
return g.__burnless_db ?? lazySyncPostgresOrThrow();
|
|
3730
|
+
}
|
|
3731
|
+
var db = new Proxy({}, {
|
|
3732
|
+
get(_t, prop, receiver) {
|
|
3733
|
+
const live = resolveLiveDb();
|
|
3734
|
+
const value = Reflect.get(live, prop, receiver);
|
|
3735
|
+
return typeof value === "function" ? value.bind(live) : value;
|
|
3736
|
+
},
|
|
3737
|
+
has(_t, prop) {
|
|
3738
|
+
return Reflect.has(resolveLiveDb(), prop);
|
|
3739
|
+
},
|
|
3740
|
+
// Drizzle's `is(db, PgDatabase)` (used by @auth/drizzle-adapter at import time,
|
|
3741
|
+
// before initDatabase() runs) checks the prototype chain. Both postgres-js and
|
|
3742
|
+
// PGLite drizzle instances are PgDatabase subclasses (public type is
|
|
3743
|
+
// PostgresJsDatabase, a PgDatabase). Forwarding getPrototypeOf to PgDatabase.prototype
|
|
3744
|
+
// makes the Proxy type-detectable WITHOUT a live instance — so adapter construction
|
|
3745
|
+
// works during `next build` page-data collection (no DATABASE_URL yet). spec §6.
|
|
3746
|
+
getPrototypeOf() {
|
|
3747
|
+
return PgDatabase.prototype;
|
|
3748
|
+
}
|
|
3749
|
+
});
|
|
3750
|
+
async function initDatabase() {
|
|
3751
|
+
if (g.__burnless_db) return g.__burnless_db;
|
|
3752
|
+
const resolved = resolveDriver(process.env);
|
|
3753
|
+
const handle = await createClient(resolved);
|
|
3754
|
+
if (shouldAutoMigrate(handle.dialect, process.env)) {
|
|
3755
|
+
await applyMigrations(handle);
|
|
3756
|
+
}
|
|
3757
|
+
g.__burnless_db = handle.db;
|
|
3758
|
+
g.__burnless_handle = handle;
|
|
3759
|
+
if (handle.dialect === "pglite") g.__burnless_pglite = handle.raw;
|
|
3760
|
+
return handle.db;
|
|
3761
|
+
}
|
|
3762
|
+
function isDatabaseBooted() {
|
|
3763
|
+
return Boolean(g.__burnless_db);
|
|
3764
|
+
}
|
|
3765
|
+
async function closeDatabase() {
|
|
3766
|
+
const handle = g.__burnless_handle;
|
|
3767
|
+
delete g.__burnless_db;
|
|
3768
|
+
delete g.__burnless_handle;
|
|
3769
|
+
delete g.__burnless_pglite;
|
|
3770
|
+
if (handle) await handle.close();
|
|
3771
|
+
}
|
|
3772
|
+
function execRows(result) {
|
|
3773
|
+
return Array.isArray(result) ? result : result.rows ?? [];
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3776
|
+
export {
|
|
3777
|
+
companyStageEnum,
|
|
3778
|
+
businessModelEnum,
|
|
3779
|
+
accountTypeEnum,
|
|
3780
|
+
accountCategoryEnum,
|
|
3781
|
+
transactionSourceEnum,
|
|
3782
|
+
scenarioSourceEnum,
|
|
3783
|
+
scenarioStatusEnum,
|
|
3784
|
+
scenarioOverrideActionEnum,
|
|
3785
|
+
forecastMethodEnum,
|
|
3786
|
+
expenseFrequencyEnum,
|
|
3787
|
+
memberRoleEnum,
|
|
3788
|
+
integrationTypeEnum,
|
|
3789
|
+
integrationStatusEnum,
|
|
3790
|
+
fundingRoundTypeEnum,
|
|
3791
|
+
revenueStreamTypeEnum,
|
|
3792
|
+
metricCategoryEnum,
|
|
3793
|
+
aiMessageRoleEnum,
|
|
3794
|
+
aiDataModeEnum,
|
|
3795
|
+
aiWriteModeEnum,
|
|
3796
|
+
aiProviderKindEnum,
|
|
3797
|
+
aiApiKeyModeEnum,
|
|
3798
|
+
aiProviderModelSourceEnum,
|
|
3799
|
+
aiPermissionModeEnum,
|
|
3800
|
+
aiToolPermissionDecisionEnum,
|
|
3801
|
+
headcountEmployeeTypeEnum,
|
|
3802
|
+
notificationSeverityEnum,
|
|
3803
|
+
mcpTransportEnum,
|
|
3804
|
+
mcpOwnerScopeEnum,
|
|
3805
|
+
mcpAuthTypeEnum,
|
|
3806
|
+
mcpConnectionStatusEnum,
|
|
3807
|
+
mcpToolPermEnum,
|
|
3808
|
+
users,
|
|
3809
|
+
accounts,
|
|
3810
|
+
sessions,
|
|
3811
|
+
verificationTokens,
|
|
3812
|
+
dataRegionEnum,
|
|
3813
|
+
companies,
|
|
3814
|
+
companyMembers,
|
|
3815
|
+
departments,
|
|
3816
|
+
financialAccounts,
|
|
3817
|
+
transactions,
|
|
3818
|
+
importBatchStatusEnum,
|
|
3819
|
+
importBatches,
|
|
3820
|
+
scenarios,
|
|
3821
|
+
scenarioOverrides,
|
|
3822
|
+
forecastLines,
|
|
3823
|
+
forecastValues,
|
|
3824
|
+
headcountPlans,
|
|
3825
|
+
equityGrantTypeEnum,
|
|
3826
|
+
shareClassTypeEnum,
|
|
3827
|
+
equityGrants,
|
|
3828
|
+
bonusTypeEnum,
|
|
3829
|
+
bonuses,
|
|
3830
|
+
salaryChanges,
|
|
3831
|
+
revenueStreams,
|
|
3832
|
+
fundingRounds,
|
|
3833
|
+
fundingRoundInvestors,
|
|
3834
|
+
shareClasses,
|
|
3835
|
+
optionPools,
|
|
3836
|
+
metrics,
|
|
3837
|
+
integrations,
|
|
3838
|
+
mcpConnections,
|
|
3839
|
+
mcpCredentials,
|
|
3840
|
+
mcpToolPrefs,
|
|
3841
|
+
apiTokens,
|
|
3842
|
+
oauthClients,
|
|
3843
|
+
oauthAuthCodes,
|
|
3844
|
+
oauthTokens,
|
|
3845
|
+
aiFeatureFlags,
|
|
3846
|
+
aiProviders,
|
|
3847
|
+
aiProviderModels,
|
|
3848
|
+
aiConversations,
|
|
3849
|
+
insightInvalidations,
|
|
3850
|
+
aiInsightCacheTypeEnum,
|
|
3851
|
+
aiInsightCache,
|
|
3852
|
+
aiMessages,
|
|
3853
|
+
aiPendingActionKindEnum,
|
|
3854
|
+
aiPendingActions,
|
|
3855
|
+
dashboardModeEnum,
|
|
3856
|
+
dashboardPreferences,
|
|
3857
|
+
usersRelations,
|
|
3858
|
+
companiesRelations,
|
|
3859
|
+
dashboardPreferencesRelations,
|
|
3860
|
+
companyMembersRelations,
|
|
3861
|
+
financialAccountsRelations,
|
|
3862
|
+
transactionsRelations,
|
|
3863
|
+
importBatchesRelations,
|
|
3864
|
+
scenariosRelations,
|
|
3865
|
+
scenarioOverridesRelations,
|
|
3866
|
+
forecastLinesRelations,
|
|
3867
|
+
forecastValuesRelations,
|
|
3868
|
+
departmentsRelations,
|
|
3869
|
+
headcountPlansRelations,
|
|
3870
|
+
salaryChangesRelations,
|
|
3871
|
+
bonusesRelations,
|
|
3872
|
+
equityGrantsRelations,
|
|
3873
|
+
revenueStreamsRelations,
|
|
3874
|
+
fundingRoundsRelations,
|
|
3875
|
+
fundingRoundInvestorsRelations,
|
|
3876
|
+
shareClassesRelations,
|
|
3877
|
+
optionPoolsRelations,
|
|
3878
|
+
metricsRelations,
|
|
3879
|
+
integrationsRelations,
|
|
3880
|
+
aiFeatureFlagsRelations,
|
|
3881
|
+
aiProvidersRelations,
|
|
3882
|
+
aiProviderModelsRelations,
|
|
3883
|
+
aiConversationsRelations,
|
|
3884
|
+
aiInsightCacheRelations,
|
|
3885
|
+
aiMessagesRelations,
|
|
3886
|
+
weeklyDigests,
|
|
3887
|
+
weeklyDigestsRelations,
|
|
3888
|
+
consentPurposeEnum,
|
|
3889
|
+
privacyConsents,
|
|
3890
|
+
privacyConsentsRelations,
|
|
3891
|
+
merchantCategoryMappings,
|
|
3892
|
+
merchantCategoryMappingsRelations,
|
|
3893
|
+
auditActionEnum,
|
|
3894
|
+
auditEntityTypeEnum,
|
|
3895
|
+
financialAuditLogs,
|
|
3896
|
+
financialAuditLogsRelations,
|
|
3897
|
+
scheduledJobActionKindEnum,
|
|
3898
|
+
scheduledJobStatusEnum,
|
|
3899
|
+
scheduledJobNotifyPolicyEnum,
|
|
3900
|
+
scheduledJobRunStatusEnum,
|
|
3901
|
+
scheduledJobRunTriggerEnum,
|
|
3902
|
+
scheduledJobs,
|
|
3903
|
+
scheduledJobRuns,
|
|
3904
|
+
aiToolAuditLogStatusEnum,
|
|
3905
|
+
aiToolAuditLogs,
|
|
3906
|
+
inviteCodeTypeEnum,
|
|
3907
|
+
inviteCodes,
|
|
3908
|
+
inviteCodeRedemptions,
|
|
3909
|
+
inviteCodesRelations,
|
|
3910
|
+
inviteCodeRedemptionsRelations,
|
|
3911
|
+
aiToolAuditLogsRelations,
|
|
3912
|
+
mcpConnectionsRelations,
|
|
3913
|
+
mcpCredentialsRelations,
|
|
3914
|
+
mcpToolPrefsRelations,
|
|
3915
|
+
quickActionModeEnum,
|
|
3916
|
+
userPreferences,
|
|
3917
|
+
userPreferencesRelations,
|
|
3918
|
+
aiPermissionDefaults,
|
|
3919
|
+
aiUsageLogs,
|
|
3920
|
+
exportLogs,
|
|
3921
|
+
notifications,
|
|
3922
|
+
schema_exports,
|
|
3923
|
+
BurnlessDbConfigError,
|
|
3924
|
+
resolveDriver,
|
|
3925
|
+
createClient,
|
|
3926
|
+
shouldAutoMigrate,
|
|
3927
|
+
applyMigrations,
|
|
3928
|
+
getCompanyForUser,
|
|
3929
|
+
getUserWithCompany,
|
|
3930
|
+
getCompanyById,
|
|
3931
|
+
listCompaniesForUser,
|
|
3932
|
+
getScenarioForCompany,
|
|
3933
|
+
getDefaultScenario,
|
|
3934
|
+
getScenarioData,
|
|
3935
|
+
getScenarioDataWithValues,
|
|
3936
|
+
findByIdForCompany,
|
|
3937
|
+
updateForCompany,
|
|
3938
|
+
deleteForCompany,
|
|
3939
|
+
listForCompany,
|
|
3940
|
+
getOverridesForScenario,
|
|
3941
|
+
upsertOverride,
|
|
3942
|
+
deleteOverride,
|
|
3943
|
+
deleteOverrideByEntity,
|
|
3944
|
+
getOverrideCount,
|
|
3945
|
+
getOverrideCounts,
|
|
3946
|
+
getOverrideBreakdown,
|
|
3947
|
+
listInvestorsForRound,
|
|
3948
|
+
listShareClasses,
|
|
3949
|
+
listOptionPools,
|
|
3950
|
+
createShareClass,
|
|
3951
|
+
updateShareClass,
|
|
3952
|
+
softDeleteShareClass,
|
|
3953
|
+
createOptionPool,
|
|
3954
|
+
updateOptionPool,
|
|
3955
|
+
softDeleteOptionPool,
|
|
3956
|
+
countOptionPools,
|
|
3957
|
+
resolveEntities,
|
|
3958
|
+
getResolvedData,
|
|
3959
|
+
planScenarioInsert,
|
|
3960
|
+
commitScenarioPlan,
|
|
3961
|
+
planScenarioUpdate,
|
|
3962
|
+
planScenarioDelete,
|
|
3963
|
+
scenarioInsert,
|
|
3964
|
+
scenarioUpdate,
|
|
3965
|
+
scenarioDelete,
|
|
3966
|
+
promoteScenario,
|
|
3967
|
+
hasFinancialData,
|
|
3968
|
+
SCENARIO_ENTITY_TYPES,
|
|
3969
|
+
listSalaryChanges,
|
|
3970
|
+
listResolvedSalaryChanges,
|
|
3971
|
+
createSalaryChange,
|
|
3972
|
+
updateSalaryChange,
|
|
3973
|
+
removeSalaryChange,
|
|
3974
|
+
listBonuses,
|
|
3975
|
+
listResolvedBonuses,
|
|
3976
|
+
createBonus,
|
|
3977
|
+
updateBonus,
|
|
3978
|
+
removeBonus,
|
|
3979
|
+
listEquityGrants,
|
|
3980
|
+
listResolvedEquityGrants,
|
|
3981
|
+
createEquityGrant,
|
|
3982
|
+
updateEquityGrant,
|
|
3983
|
+
removeEquityGrant,
|
|
3984
|
+
getPermissionDefaults,
|
|
3985
|
+
upsertPermissionDefaults,
|
|
3986
|
+
getSessionGrants,
|
|
3987
|
+
grantSessionPermission,
|
|
3988
|
+
resetSessionGrants,
|
|
3989
|
+
getSessionDisabledTools,
|
|
3990
|
+
setSessionDisabledTool,
|
|
3991
|
+
resetSessionDisabledTools,
|
|
3992
|
+
createPendingAction,
|
|
3993
|
+
getActivePendingAction,
|
|
3994
|
+
resolvePendingAction,
|
|
3995
|
+
updatePendingActionTimeline,
|
|
3996
|
+
LOCAL_OWNER_EMAIL,
|
|
3997
|
+
LOCAL_OWNER_ID,
|
|
3998
|
+
LOCAL_OWNER_COMPANY_ID,
|
|
3999
|
+
createOwnerUserIfNone,
|
|
4000
|
+
createOwnerCompanyIfNone,
|
|
4001
|
+
getOwnerUser,
|
|
4002
|
+
isOwnerClaimed,
|
|
4003
|
+
listUsers,
|
|
4004
|
+
createUser,
|
|
4005
|
+
setUserPassword,
|
|
4006
|
+
encryptSecret,
|
|
4007
|
+
decryptSecret,
|
|
4008
|
+
encryptJson,
|
|
4009
|
+
decryptJson,
|
|
4010
|
+
__resetSecretsKeyCache,
|
|
4011
|
+
slugifyConnectionName,
|
|
4012
|
+
listVisibleConnections,
|
|
4013
|
+
getVisibleConnection,
|
|
4014
|
+
createMcpConnection,
|
|
4015
|
+
updateMcpConnection,
|
|
4016
|
+
deleteMcpConnection,
|
|
4017
|
+
saveMcpCredentials,
|
|
4018
|
+
getMcpCredentialsRow,
|
|
4019
|
+
getDecryptedMcpSecret,
|
|
4020
|
+
listMcpToolPrefs,
|
|
4021
|
+
upsertMcpToolPref,
|
|
4022
|
+
getDisabledMcpConnectionIds,
|
|
4023
|
+
getDisabledBuiltinTools,
|
|
4024
|
+
upsertUserPreferences,
|
|
4025
|
+
listAiProviders,
|
|
4026
|
+
getAiProvider,
|
|
4027
|
+
createAiProvider,
|
|
4028
|
+
updateAiProvider,
|
|
4029
|
+
deleteAiProvider,
|
|
4030
|
+
setDefaultAiProvider,
|
|
4031
|
+
getDefaultAiProvider,
|
|
4032
|
+
getDecryptedProviderKey,
|
|
4033
|
+
listAiProviderModels,
|
|
4034
|
+
addAiProviderModel,
|
|
4035
|
+
setDefaultAiProviderModel,
|
|
4036
|
+
getResolvedDefaultModelId,
|
|
4037
|
+
sha256hex,
|
|
4038
|
+
generateSecretToken,
|
|
4039
|
+
roleScopeCap,
|
|
4040
|
+
mintApiToken,
|
|
4041
|
+
listApiTokensForUser,
|
|
4042
|
+
revokeApiToken,
|
|
4043
|
+
findApiTokenByHash,
|
|
4044
|
+
touchApiTokenLastUsed,
|
|
4045
|
+
createNotification,
|
|
4046
|
+
listNotificationsForUser,
|
|
4047
|
+
countUnreadNotifications,
|
|
4048
|
+
markNotificationsRead,
|
|
4049
|
+
createOauthClient,
|
|
4050
|
+
getOauthClientById,
|
|
4051
|
+
createAuthCode,
|
|
4052
|
+
consumeAuthCode,
|
|
4053
|
+
issueOauthTokens,
|
|
4054
|
+
findOauthTokenByAccessHash,
|
|
4055
|
+
rotateRefreshToken,
|
|
4056
|
+
listOauthGrantsForUser,
|
|
4057
|
+
revokeOauthGrant,
|
|
4058
|
+
createScheduledJob,
|
|
4059
|
+
getScheduledJob,
|
|
4060
|
+
getScheduledJobById,
|
|
4061
|
+
listScheduledJobs,
|
|
4062
|
+
countScheduledJobs,
|
|
4063
|
+
updateScheduledJob,
|
|
4064
|
+
softDeleteScheduledJob,
|
|
4065
|
+
listDueScheduledJobs,
|
|
4066
|
+
startScheduledJobRun,
|
|
4067
|
+
finishScheduledJobRun,
|
|
4068
|
+
recordMissedRun,
|
|
4069
|
+
listScheduledJobRuns,
|
|
4070
|
+
db,
|
|
4071
|
+
initDatabase,
|
|
4072
|
+
isDatabaseBooted,
|
|
4073
|
+
closeDatabase,
|
|
4074
|
+
execRows
|
|
4075
|
+
};
|