@agent-native/core 0.145.6 → 0.145.8
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/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +3 -2
- package/corpus/templates/analytics/changelog/2026-08-07-analytics-no-longer-stalls-under-bursts-of-event-ingest-key-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-07-settings-skeleton-placeholders.md +6 -0
- package/corpus/templates/analytics/netlify.toml +1 -1
- package/corpus/templates/analytics/package.json +1 -0
- package/corpus/templates/analytics/scripts/migrate-production.ts +24 -0
- package/corpus/templates/analytics/server/jobs/analytics-rollup-backfill.ts +9 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +65 -10
- package/corpus/templates/analytics/server/lib/session-replay.ts +5 -5
- package/corpus/templates/analytics/server/plugins/db.ts +14 -3
- package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/chat/AGENTS.md +3 -0
- package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +21 -3
- package/corpus/templates/crm/app/components/crm/settings/ConnectionSettings.tsx +23 -5
- package/corpus/templates/crm/app/components/crm/settings/FieldsSettings.tsx +24 -5
- package/corpus/templates/crm/app/components/crm/settings/ListsSettings.tsx +23 -3
- package/corpus/templates/crm/changelog/2026-08-07-settings-loading-skeletons.md +6 -0
- package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +5 -1
- package/dist/client/components/ApiKeySettings.js +2 -5
- package/dist/client/org/TeamPage.js +4 -2
- package/dist/client/settings/AccountSettingsCard.js +2 -1
- package/dist/client/settings/AgentsSection.js +2 -1
- package/dist/client/settings/SecretsSection.js +3 -2
- package/dist/client/settings/SettingsPanel.js +6 -12
- package/dist/client/settings/SettingsSkeleton.d.ts +12 -0
- package/dist/client/settings/SettingsSkeleton.js +12 -0
- package/dist/client/settings/VoiceTranscriptionSection.js +5 -4
- package/dist/client/settings/index.d.ts +1 -0
- package/dist/client/settings/index.js +1 -0
- package/dist/client/use-db-sync.js +4 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +13 -7
- package/dist/db/client.js +124 -7
- package/dist/db/ddl-guard.js +3 -1
- package/dist/db/ensure-additive-columns.d.ts +2 -0
- package/dist/db/ensure-additive-columns.js +5 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +2 -2
- package/dist/db/migrations.d.ts +11 -54
- package/dist/db/migrations.js +70 -0
- package/dist/jobs/index.d.ts +1 -0
- package/dist/jobs/index.js +1 -0
- package/dist/jobs/run-history.d.ts +4 -0
- package/dist/jobs/run-history.js +44 -0
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
- package/dist/provider-api/actions/provider-api.d.ts +12 -12
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-chat-plugin.js +6 -4
- package/dist/server/auth-plugin.js +5 -0
- package/dist/server/better-auth-instance.js +0 -61
- package/dist/server/better-auth-migrations.d.ts +8 -0
- package/dist/server/better-auth-migrations.js +179 -0
- package/dist/server/edge.d.ts +2 -0
- package/dist/server/edge.js +2 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -0
- package/dist/server/release-migrations.d.ts +8 -0
- package/dist/server/release-migrations.js +24 -0
- package/dist/server/sweep-runtime.d.ts +1 -1
- package/dist/server/sweep-runtime.js +15 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
- package/dist/templates/chat/AGENTS.md +3 -0
- package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +5 -1
- package/dist/templates/default/AGENTS.md +3 -0
- package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -1
- package/dist/templates/workspace-core/AGENTS.md +3 -0
- package/dist/templates/workspace-root/AGENTS.md +3 -0
- package/package.json +1 -1
- package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
- package/src/templates/chat/AGENTS.md +3 -0
- package/src/templates/default/.agents/skills/frontend-design/SKILL.md +5 -1
- package/src/templates/default/AGENTS.md +3 -0
- package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -1
- package/src/templates/workspace-core/AGENTS.md +3 -0
- package/src/templates/workspace-root/AGENTS.md +3 -0
package/dist/db/client.js
CHANGED
|
@@ -765,6 +765,47 @@ export function isServerlessRuntime() {
|
|
|
765
765
|
!!process.env.LAMBDA_TASK_ROOT ||
|
|
766
766
|
!!process.env.CF_PAGES);
|
|
767
767
|
}
|
|
768
|
+
/**
|
|
769
|
+
* True for production serverless execution contexts. Netlify also exposes
|
|
770
|
+
* `NETLIFY=true` in its build environment, so build-time code that owns
|
|
771
|
+
* migrations must use `withMigrationRuntime()` explicitly.
|
|
772
|
+
*/
|
|
773
|
+
export function isProductionServerlessFunctionRuntime(env = process.env) {
|
|
774
|
+
if (env.NODE_ENV !== "production" || env.NETLIFY_LOCAL === "true") {
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
return Boolean(env.NETLIFY === "true" ||
|
|
778
|
+
env.NETLIFY_FUNCTION_NAME ||
|
|
779
|
+
env.AWS_LAMBDA_FUNCTION_NAME ||
|
|
780
|
+
env.AWS_LAMBDA_FUNCTION_VERSION ||
|
|
781
|
+
env.LAMBDA_TASK_ROOT ||
|
|
782
|
+
env.AWS_EXECUTION_ENV?.startsWith("AWS_Lambda") === true ||
|
|
783
|
+
env.VERCEL_FUNCTION_ID ||
|
|
784
|
+
env.VERCEL_REGION ||
|
|
785
|
+
env.VERCEL === "1");
|
|
786
|
+
}
|
|
787
|
+
const SCHEMA_MUTATION_STATEMENT = /^\s*(?:CREATE|ALTER|DROP|TRUNCATE|COMMENT|REINDEX|GRANT|REVOKE)\b/i;
|
|
788
|
+
function rawSql(statement) {
|
|
789
|
+
return typeof statement === "string" ? statement : statement.sql;
|
|
790
|
+
}
|
|
791
|
+
/** True for statements that change database schema or database privileges. */
|
|
792
|
+
export function isSchemaMutationStatement(statement) {
|
|
793
|
+
return SCHEMA_MUTATION_STATEMENT.test(rawSql(statement));
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Request functions must never be able to prepare or mutate schema. Throwing
|
|
797
|
+
* here makes an unconverted ensureTable path fail loudly instead of silently
|
|
798
|
+
* reporting success against a missing table. The release migration wrapper
|
|
799
|
+
* is the only supported production opt-in.
|
|
800
|
+
*/
|
|
801
|
+
export function assertSchemaMutationAllowed(statement) {
|
|
802
|
+
const migrationRuntime = globalThis.__AGENT_NATIVE_MIGRATION_RUNTIME__ === true;
|
|
803
|
+
if (isProductionServerlessFunctionRuntime() &&
|
|
804
|
+
!migrationRuntime &&
|
|
805
|
+
isSchemaMutationStatement(statement)) {
|
|
806
|
+
throw new Error("Schema mutation attempted in a production serverless request. Run migrations in the release job instead.");
|
|
807
|
+
}
|
|
808
|
+
}
|
|
768
809
|
/**
|
|
769
810
|
* postgres.js pool options tuned per runtime. idle_timeout is shortened on
|
|
770
811
|
* serverless so a thawed-but-idle instance releases its connections quickly.
|
|
@@ -774,10 +815,27 @@ export function isServerlessRuntime() {
|
|
|
774
815
|
* process, not one per consumer. See {@link neonPoolMax} for why the cap must
|
|
775
816
|
* leave room for concurrency.
|
|
776
817
|
*/
|
|
818
|
+
/**
|
|
819
|
+
* Identifies the connection in `pg_stat_activity.application_name`.
|
|
820
|
+
*
|
|
821
|
+
* Without it every backend reports `pgbouncer` and a runaway query is
|
|
822
|
+
* anonymous. A 58 MB `SELECT id, config FROM dashboards` ran 20-wide against
|
|
823
|
+
* production and could not be traced to a caller: it appears nowhere in the
|
|
824
|
+
* repo or any built bundle, and `pg_stat_statements` is not installed. This is
|
|
825
|
+
* the cheapest thing that would have named it.
|
|
826
|
+
*/
|
|
827
|
+
function poolApplicationName() {
|
|
828
|
+
const site = process.env.SITE_NAME ??
|
|
829
|
+
process.env.NETLIFY_SITE_NAME ??
|
|
830
|
+
process.env.AGENT_NATIVE_APP_NAME ??
|
|
831
|
+
"app";
|
|
832
|
+
return `agent-native:${site}`.slice(0, 63);
|
|
833
|
+
}
|
|
777
834
|
export function pgPoolOptions(url) {
|
|
778
835
|
const serverless = isServerlessRuntime();
|
|
779
836
|
return {
|
|
780
837
|
onnotice: () => { },
|
|
838
|
+
connection: { application_name: poolApplicationName() },
|
|
781
839
|
max: serverless ? serverlessPoolMax() : 20,
|
|
782
840
|
idle_timeout: serverless ? 20 : 240,
|
|
783
841
|
max_lifetime: 60 * 30,
|
|
@@ -785,6 +843,7 @@ export function pgPoolOptions(url) {
|
|
|
785
843
|
...(serverless
|
|
786
844
|
? {
|
|
787
845
|
connection: {
|
|
846
|
+
application_name: poolApplicationName(),
|
|
788
847
|
idle_in_transaction_session_timeout: 30_000,
|
|
789
848
|
},
|
|
790
849
|
}
|
|
@@ -1504,7 +1563,36 @@ async function createDbExecInternal(config = {}, trackSingletonResources = false
|
|
|
1504
1563
|
};
|
|
1505
1564
|
}
|
|
1506
1565
|
export async function createDbExec(config = {}) {
|
|
1507
|
-
|
|
1566
|
+
const exec = await createDbExecInternal(config, false);
|
|
1567
|
+
return guardSchemaMutations(exec);
|
|
1568
|
+
}
|
|
1569
|
+
function guardSchemaMutations(exec) {
|
|
1570
|
+
const guarded = {
|
|
1571
|
+
async execute(statement) {
|
|
1572
|
+
assertSchemaMutationAllowed(statement);
|
|
1573
|
+
return exec.execute(statement);
|
|
1574
|
+
},
|
|
1575
|
+
};
|
|
1576
|
+
if (exec.atomicBatch) {
|
|
1577
|
+
guarded.atomicBatch = async (statements) => {
|
|
1578
|
+
for (const statement of statements) {
|
|
1579
|
+
assertSchemaMutationAllowed(statement);
|
|
1580
|
+
}
|
|
1581
|
+
return exec.atomicBatch(statements);
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
if (exec.transaction) {
|
|
1585
|
+
guarded.transaction = (fn) => exec.transaction((tx) => fn({
|
|
1586
|
+
...tx,
|
|
1587
|
+
execute: async (statement) => {
|
|
1588
|
+
assertSchemaMutationAllowed(statement);
|
|
1589
|
+
return tx.execute(statement);
|
|
1590
|
+
},
|
|
1591
|
+
}));
|
|
1592
|
+
}
|
|
1593
|
+
if (exec.close)
|
|
1594
|
+
guarded.close = () => exec.close();
|
|
1595
|
+
return guarded;
|
|
1508
1596
|
}
|
|
1509
1597
|
async function initClient() {
|
|
1510
1598
|
if (_exec)
|
|
@@ -1561,6 +1649,7 @@ export function getDbExec() {
|
|
|
1561
1649
|
return sql;
|
|
1562
1650
|
}
|
|
1563
1651
|
async function execAnnotated(s) {
|
|
1652
|
+
assertSchemaMutationAllowed(s);
|
|
1564
1653
|
try {
|
|
1565
1654
|
return await _exec.execute(sanitize(s));
|
|
1566
1655
|
}
|
|
@@ -1571,6 +1660,7 @@ export function getDbExec() {
|
|
|
1571
1660
|
// Return a proxy that lazy-inits on first call
|
|
1572
1661
|
const proxy = {
|
|
1573
1662
|
async execute(sql) {
|
|
1663
|
+
assertSchemaMutationAllowed(sql);
|
|
1574
1664
|
if (!_initPromise)
|
|
1575
1665
|
_initPromise = initClient();
|
|
1576
1666
|
try {
|
|
@@ -1588,11 +1678,19 @@ export function getDbExec() {
|
|
|
1588
1678
|
const wrapper = {
|
|
1589
1679
|
execute: (s) => execAnnotated(s),
|
|
1590
1680
|
atomicBatch: _exec.atomicBatch
|
|
1591
|
-
?
|
|
1681
|
+
? async (statements) => {
|
|
1682
|
+
for (const statement of statements) {
|
|
1683
|
+
assertSchemaMutationAllowed(statement);
|
|
1684
|
+
}
|
|
1685
|
+
return _exec.atomicBatch(statements.map((s) => sanitize(s)));
|
|
1686
|
+
}
|
|
1592
1687
|
: undefined,
|
|
1593
1688
|
transaction: _exec.transaction
|
|
1594
1689
|
? (fn) => _exec.transaction((tx) => fn({
|
|
1595
|
-
execute: (s) =>
|
|
1690
|
+
execute: (s) => {
|
|
1691
|
+
assertSchemaMutationAllowed(s);
|
|
1692
|
+
return tx.execute(sanitize(s));
|
|
1693
|
+
},
|
|
1596
1694
|
transaction: tx.transaction,
|
|
1597
1695
|
}))
|
|
1598
1696
|
: undefined,
|
|
@@ -1614,11 +1712,19 @@ export function getDbExec() {
|
|
|
1614
1712
|
const wrapper = {
|
|
1615
1713
|
execute: (s) => execAnnotated(s),
|
|
1616
1714
|
atomicBatch: _exec.atomicBatch
|
|
1617
|
-
?
|
|
1715
|
+
? async (statements) => {
|
|
1716
|
+
for (const statement of statements) {
|
|
1717
|
+
assertSchemaMutationAllowed(statement);
|
|
1718
|
+
}
|
|
1719
|
+
return _exec.atomicBatch(statements.map((s) => sanitize(s)));
|
|
1720
|
+
}
|
|
1618
1721
|
: undefined,
|
|
1619
1722
|
transaction: _exec.transaction
|
|
1620
1723
|
? (innerFn) => _exec.transaction((tx) => innerFn({
|
|
1621
|
-
execute: (s) =>
|
|
1724
|
+
execute: (s) => {
|
|
1725
|
+
assertSchemaMutationAllowed(s);
|
|
1726
|
+
return tx.execute(sanitize(s));
|
|
1727
|
+
},
|
|
1622
1728
|
transaction: tx.transaction,
|
|
1623
1729
|
}))
|
|
1624
1730
|
: undefined,
|
|
@@ -1626,7 +1732,10 @@ export function getDbExec() {
|
|
|
1626
1732
|
Object.assign(proxy, wrapper);
|
|
1627
1733
|
if (_exec.transaction) {
|
|
1628
1734
|
return _exec.transaction((tx) => fn({
|
|
1629
|
-
execute: (s) =>
|
|
1735
|
+
execute: (s) => {
|
|
1736
|
+
assertSchemaMutationAllowed(s);
|
|
1737
|
+
return tx.execute(sanitize(s));
|
|
1738
|
+
},
|
|
1630
1739
|
transaction: tx.transaction,
|
|
1631
1740
|
}));
|
|
1632
1741
|
}
|
|
@@ -1636,6 +1745,9 @@ export function getDbExec() {
|
|
|
1636
1745
|
return explicitTransaction(wrapper.execute)(fn);
|
|
1637
1746
|
},
|
|
1638
1747
|
async atomicBatch(statements) {
|
|
1748
|
+
for (const statement of statements) {
|
|
1749
|
+
assertSchemaMutationAllowed(statement);
|
|
1750
|
+
}
|
|
1639
1751
|
if (!_initPromise)
|
|
1640
1752
|
_initPromise = initClient();
|
|
1641
1753
|
try {
|
|
@@ -1649,7 +1761,12 @@ export function getDbExec() {
|
|
|
1649
1761
|
if (!_exec.atomicBatch) {
|
|
1650
1762
|
throw new Error("This database does not support atomic batches.");
|
|
1651
1763
|
}
|
|
1652
|
-
const batch =
|
|
1764
|
+
const batch = async (items) => {
|
|
1765
|
+
for (const item of items) {
|
|
1766
|
+
assertSchemaMutationAllowed(item);
|
|
1767
|
+
}
|
|
1768
|
+
return _exec.atomicBatch(items.map((item) => sanitize(item)));
|
|
1769
|
+
};
|
|
1653
1770
|
Object.assign(proxy, { atomicBatch: batch });
|
|
1654
1771
|
return batch(statements);
|
|
1655
1772
|
},
|
package/dist/db/ddl-guard.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* (local dev) there is no such lock problem, so callers keep their existing
|
|
29
29
|
* behaviour there.
|
|
30
30
|
*/
|
|
31
|
-
import { isPostgres,
|
|
31
|
+
import { getDbExec, isPostgres, isProductionServerlessFunctionRuntime, } from "./client.js";
|
|
32
32
|
const PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
33
33
|
/**
|
|
34
34
|
* Skip on-demand schema DDL entirely. For deployments that run real migrations,
|
|
@@ -45,6 +45,8 @@ const PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
45
45
|
* down the DDL path this flag exists to avoid.
|
|
46
46
|
*/
|
|
47
47
|
function schemaEnsureDisabled() {
|
|
48
|
+
if (isProductionServerlessFunctionRuntime())
|
|
49
|
+
return true;
|
|
48
50
|
const raw = process.env.AGENT_NATIVE_SKIP_ENSURE_TABLES?.trim();
|
|
49
51
|
return !!raw && ["1", "true", "yes", "on"].includes(raw.toLowerCase());
|
|
50
52
|
}
|
|
@@ -67,6 +67,8 @@ export interface EnsureAdditiveColumnsOptions {
|
|
|
67
67
|
logger?: EnsureAdditiveColumnsLogger;
|
|
68
68
|
}
|
|
69
69
|
export interface EnsureAdditiveColumnsResult {
|
|
70
|
+
/** Whether this invocation actually inspected the live schema. */
|
|
71
|
+
mode: "checked" | "skipped-serverless";
|
|
70
72
|
/** `"table.column"` entries that were successfully added. */
|
|
71
73
|
applied: string[];
|
|
72
74
|
/** Declared columns that were intentionally left unpatched, with why. */
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
* unreadable) must never crash boot — the caller decides whether/how to
|
|
55
55
|
* surface `errors` in the returned summary.
|
|
56
56
|
*/
|
|
57
|
-
import { isPostgres } from "./client.js";
|
|
57
|
+
import { isPostgres, isProductionServerlessFunctionRuntime, } from "./client.js";
|
|
58
58
|
const PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
59
59
|
const defaultLogger = {
|
|
60
60
|
info: (message) => console.log(message),
|
|
@@ -62,7 +62,7 @@ const defaultLogger = {
|
|
|
62
62
|
error: (message) => console.error(message),
|
|
63
63
|
};
|
|
64
64
|
function emptyResult() {
|
|
65
|
-
return { applied: [], skipped: [], errors: [] };
|
|
65
|
+
return { mode: "checked", applied: [], skipped: [], errors: [] };
|
|
66
66
|
}
|
|
67
67
|
/**
|
|
68
68
|
* Resolve `getTableConfig` for the dialect currently in effect. Both
|
|
@@ -246,6 +246,9 @@ function describeSchemaDriftError(err) {
|
|
|
246
246
|
*/
|
|
247
247
|
export async function ensureAdditiveColumns(options) {
|
|
248
248
|
const { db, tables, logger = defaultLogger } = options;
|
|
249
|
+
if (isProductionServerlessFunctionRuntime()) {
|
|
250
|
+
return { ...emptyResult(), mode: "skipped-serverless" };
|
|
251
|
+
}
|
|
249
252
|
const result = emptyResult();
|
|
250
253
|
let getTableConfig;
|
|
251
254
|
try {
|
package/dist/db/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export declare function createDb(config: DbConfig): Promise<(import("drizzle-orm
|
|
|
21
21
|
})>;
|
|
22
22
|
export type DrizzleDb = Awaited<ReturnType<typeof createDb>>;
|
|
23
23
|
export { createGetDb } from "./create-get-db.js";
|
|
24
|
-
export { deferMigration, MIGRATION_DEFERRED, runMigrations, type MigrationEntry, type MigrationRunResult, type MigrationSql, } from "./migrations.js";
|
|
25
|
-
export { getDbExec, createDbExec, getDatabaseUrl, getDialect, isLocalDatabase, isPostgres, intType, closeDbExec, type DbExec, type DbExecConfig, type DbExecQuery, type DbExecStatement, type Dialect, } from "./client.js";
|
|
24
|
+
export { deferMigration, MIGRATION_DEFERRED, runMigrations, withMigrationRuntime, type MigrationEntry, type MigrationRunResult, type MigrationSql, } from "./migrations.js";
|
|
25
|
+
export { getDbExec, createDbExec, getDatabaseUrl, getDialect, isLocalDatabase, isPostgres, assertSchemaMutationAllowed, isSchemaMutationStatement, isProductionServerlessFunctionRuntime, intType, closeDbExec, type DbExec, type DbExecConfig, type DbExecQuery, type DbExecStatement, type Dialect, } from "./client.js";
|
|
26
26
|
export { table, text, integer, now } from "./schema.js";
|
|
27
27
|
export { ensureAdditiveColumns, type EnsureAdditiveColumnsOptions, type EnsureAdditiveColumnsResult, type EnsureAdditiveColumnsLogger, } from "./ensure-additive-columns.js";
|
package/dist/db/index.js
CHANGED
|
@@ -20,7 +20,7 @@ export async function createDb(config) {
|
|
|
20
20
|
throw new Error(`Unsupported driver: ${config.driver}`);
|
|
21
21
|
}
|
|
22
22
|
export { createGetDb } from "./create-get-db.js";
|
|
23
|
-
export { deferMigration, MIGRATION_DEFERRED, runMigrations, } from "./migrations.js";
|
|
24
|
-
export { getDbExec, createDbExec, getDatabaseUrl, getDialect, isLocalDatabase, isPostgres, intType, closeDbExec, } from "./client.js";
|
|
23
|
+
export { deferMigration, MIGRATION_DEFERRED, runMigrations, withMigrationRuntime, } from "./migrations.js";
|
|
24
|
+
export { getDbExec, createDbExec, getDatabaseUrl, getDialect, isLocalDatabase, isPostgres, assertSchemaMutationAllowed, isSchemaMutationStatement, isProductionServerlessFunctionRuntime, intType, closeDbExec, } from "./client.js";
|
|
25
25
|
export { table, text, integer, now } from "./schema.js";
|
|
26
26
|
export { ensureAdditiveColumns, } from "./ensure-additive-columns.js";
|
package/dist/db/migrations.d.ts
CHANGED
|
@@ -19,6 +19,13 @@ export declare function isDuplicateColumnError(err: unknown): boolean;
|
|
|
19
19
|
*/
|
|
20
20
|
export declare function isPermissionError(err: unknown): boolean;
|
|
21
21
|
export interface RunMigrationsOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Run migrations even inside a serverless request runtime. Off by default:
|
|
24
|
+
* see the guard in {@link runMigrations} for why request-path DDL is what
|
|
25
|
+
* took analytics down. Only set this when the caller has no scheduled or
|
|
26
|
+
* background runtime that could migrate instead.
|
|
27
|
+
*/
|
|
28
|
+
runInServerlessRequest?: boolean;
|
|
22
29
|
/**
|
|
23
30
|
* Name of the migrations bookkeeping table. REQUIRED — there is intentionally
|
|
24
31
|
* no default. Two templates that share a database (e.g. via the same Neon URL)
|
|
@@ -85,61 +92,11 @@ export interface MigrationEntry {
|
|
|
85
92
|
run?: () => Promise<MigrationRunResult>;
|
|
86
93
|
}
|
|
87
94
|
/**
|
|
88
|
-
*
|
|
89
|
-
* per-table bookkeeping row (`options.table`) so repeated boots only apply
|
|
90
|
-
* new migrations.
|
|
91
|
-
*
|
|
92
|
-
* ## Name-based tracking (why it exists)
|
|
93
|
-
*
|
|
94
|
-
* The legacy gate is purely numeric: `SELECT MAX(version)` from the
|
|
95
|
-
* bookkeeping table, then apply every entry whose `version` is greater. That
|
|
96
|
-
* scheme silently breaks down when **two independent branches each extend the
|
|
97
|
-
* same migration list with different DDL under the same version numbers** —
|
|
98
|
-
* exactly what happened to the analytics template's v75-v83 range. Branch A
|
|
99
|
-
* shipped alert-rule tables as v75-v78; branch B shipped unrelated DDL as its
|
|
100
|
-
* own v75-v83. Whichever branch deployed first recorded "v75..v83 applied"
|
|
101
|
-
* in the bookkeeping table. When the other branch merged and deployed, its
|
|
102
|
-
* migrations at those same version numbers were never applied — `MAX(version)`
|
|
103
|
-
* was already ≥ their version, so the gate treated them as done even though
|
|
104
|
-
* their actual DDL had never run. The result: `analytics_migrations` showed
|
|
105
|
-
* rows for 1..83 with no gaps, yet `analytics_alert_rules`,
|
|
106
|
-
* `analytics_alert_incidents`, and `session_recordings.network_error_count`
|
|
107
|
-
* did not exist in production. Version numbers are not a stable identity —
|
|
108
|
-
* they're just sequence position, and sequence position collides across
|
|
109
|
-
* branches.
|
|
110
|
-
*
|
|
111
|
-
* Name-based tracking fixes this by keying application on a stable, unique
|
|
112
|
-
* **string slug** instead of a position in a shared integer sequence:
|
|
113
|
-
*
|
|
114
|
-
* - Entries with a `name` are recorded in a companion table,
|
|
115
|
-
* `${table}_named` (`name TEXT PRIMARY KEY`), and APPLY IFF their name is
|
|
116
|
-
* absent from that table — completely independent of version numbers or
|
|
117
|
-
* the legacy `MAX(version)` gate. Two branches can both ship a migration
|
|
118
|
-
* named `"analytics-alert-rules-table"` at version 75, or one at version 75
|
|
119
|
-
* and the other at version 90 after a rebase — either way it applies
|
|
120
|
-
* exactly once per database, keyed on the name.
|
|
121
|
-
* - Entries WITHOUT a `name` keep the exact legacy behavior
|
|
122
|
-
* (`version > MAX(recorded version)`), so nothing changes for existing
|
|
123
|
-
* unnamed migrations.
|
|
124
|
-
* - Named migrations still execute in list order, interleaved with unnamed
|
|
125
|
-
* ones exactly as written.
|
|
126
|
-
* - When a named migration's DDL runs, we record BOTH the named row (always)
|
|
127
|
-
* and — if its `version` is greater than the current legacy max — the
|
|
128
|
-
* legacy version row too, in the SAME atomic batch/transaction as the DDL.
|
|
129
|
-
* This keeps the legacy `MAX(version)` gate monotonically advancing for
|
|
130
|
-
* any unnamed migrations that come after it in the list, while ensuring a
|
|
131
|
-
* named migration is never "double recorded" in a way that would let it
|
|
132
|
-
* re-apply.
|
|
133
|
-
* - A duplicate `name` across the migration list throws at startup — that's
|
|
134
|
-
* a programmer error (copy-paste or merge mistake), not a runtime data
|
|
135
|
-
* problem, and failing loud beats silently tracking the wrong row.
|
|
95
|
+
* Run an explicit release-time migration job with migration duty enabled.
|
|
136
96
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* same list — but if a table is EVER at risk of being shared across parallel
|
|
140
|
-
* branches (any template's own migrations qualify, since branches routinely
|
|
141
|
-
* extend the same list concurrently), giving new entries a name is what makes
|
|
142
|
-
* them immune to the collision class described above.
|
|
97
|
+
* The flag is process-local and restored even when the job fails, so a build
|
|
98
|
+
* step can opt in without creating a permanent escape hatch for request code.
|
|
143
99
|
*/
|
|
100
|
+
export declare function withMigrationRuntime<T>(run: () => Promise<T>): Promise<T>;
|
|
144
101
|
export declare function runMigrations(migrations: Array<MigrationEntry>, options: RunMigrationsOptions): NitroPluginDef;
|
|
145
102
|
export {};
|
package/dist/db/migrations.js
CHANGED
|
@@ -226,6 +226,56 @@ function resolveMigrationSql(sql, pg) {
|
|
|
226
226
|
* extend the same list concurrently), giving new entries a name is what makes
|
|
227
227
|
* them immune to the collision class described above.
|
|
228
228
|
*/
|
|
229
|
+
/**
|
|
230
|
+
* True in a production serverless runtime that serves user REQUESTS.
|
|
231
|
+
*
|
|
232
|
+
* Deliberately mirrors the analytics template's own predicate rather than
|
|
233
|
+
* importing it: `packages/core/src/db` must not depend on a template, and the
|
|
234
|
+
* agent/background modules that carry the sibling predicates would introduce a
|
|
235
|
+
* cycle here.
|
|
236
|
+
*/
|
|
237
|
+
function isServerlessRequestRuntime() {
|
|
238
|
+
if (process.env.NODE_ENV !== "production")
|
|
239
|
+
return false;
|
|
240
|
+
return (process.env.NETLIFY === "true" ||
|
|
241
|
+
Boolean(process.env.NETLIFY_FUNCTION_NAME) ||
|
|
242
|
+
Boolean(process.env.AWS_LAMBDA_FUNCTION_NAME) ||
|
|
243
|
+
Boolean(process.env.LAMBDA_TASK_ROOT) ||
|
|
244
|
+
process.env.AWS_EXECUTION_ENV?.startsWith("AWS_Lambda") === true ||
|
|
245
|
+
process.env.VERCEL === "1");
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* A runtime that is ALLOWED to migrate: scheduled jobs and durable background
|
|
249
|
+
* workers, which are off the request path and may take as long as they need.
|
|
250
|
+
*
|
|
251
|
+
* Set by whichever runtime claims migration duty. Analytics sets its own
|
|
252
|
+
* equivalent flag before running the scheduled rollup.
|
|
253
|
+
*/
|
|
254
|
+
function isMigrationAuthorizedRuntime() {
|
|
255
|
+
return (globalThis.__AGENT_NATIVE_MIGRATION_RUNTIME__ === true);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Run an explicit release-time migration job with migration duty enabled.
|
|
259
|
+
*
|
|
260
|
+
* The flag is process-local and restored even when the job fails, so a build
|
|
261
|
+
* step can opt in without creating a permanent escape hatch for request code.
|
|
262
|
+
*/
|
|
263
|
+
export async function withMigrationRuntime(run) {
|
|
264
|
+
const runtime = globalThis;
|
|
265
|
+
const previous = runtime.__AGENT_NATIVE_MIGRATION_RUNTIME__;
|
|
266
|
+
runtime.__AGENT_NATIVE_MIGRATION_RUNTIME__ = true;
|
|
267
|
+
try {
|
|
268
|
+
return await run();
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
if (previous === undefined) {
|
|
272
|
+
delete runtime.__AGENT_NATIVE_MIGRATION_RUNTIME__;
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
runtime.__AGENT_NATIVE_MIGRATION_RUNTIME__ = previous;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
229
279
|
export function runMigrations(migrations, options) {
|
|
230
280
|
const table = options?.table;
|
|
231
281
|
if (!table ||
|
|
@@ -256,6 +306,26 @@ export function runMigrations(migrations, options) {
|
|
|
256
306
|
return async () => { };
|
|
257
307
|
const namedTable = `${table}_named`;
|
|
258
308
|
return async () => {
|
|
309
|
+
// Migrations are schema DDL plus an introspection pass. On serverless
|
|
310
|
+
// "first touch" is EVERY cold start, so this lands on the critical path of
|
|
311
|
+
// a user request — measured at ~5.5-8.6s for the version check alone on a
|
|
312
|
+
// 180-table database, with 4-6 copies running concurrently under load.
|
|
313
|
+
//
|
|
314
|
+
// Guarded HERE rather than at each call site on purpose. The analytics
|
|
315
|
+
// template guarded its own runner in #2708; three core plugins
|
|
316
|
+
// (org, context-xray, observational-memory) kept calling this unguarded,
|
|
317
|
+
// so the probe storm survived the fix that was supposed to end it. A
|
|
318
|
+
// fourth special case would have shipped the same bug a fourth time.
|
|
319
|
+
//
|
|
320
|
+
// Schema still has to exist: a scheduled/background runtime sets
|
|
321
|
+
// `__AGENT_NATIVE_MIGRATION_RUNTIME__`, and `runInServerlessRequest` is
|
|
322
|
+
// the explicit opt-in for a caller that genuinely cannot defer.
|
|
323
|
+
if (options?.runInServerlessRequest !== true &&
|
|
324
|
+
isServerlessRequestRuntime() &&
|
|
325
|
+
!isMigrationAuthorizedRuntime()) {
|
|
326
|
+
console.info(`[migrations] Skipping "${table}" migrations in a serverless request runtime`);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
259
329
|
try {
|
|
260
330
|
// Check for Cloudflare D1 binding (only if DATABASE_URL not set)
|
|
261
331
|
const d1 = getDialect() === "d1"
|
package/dist/jobs/index.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export { processRecurringJobs, parseJobFrontmatter, buildJobContent, type JobFro
|
|
|
3
3
|
export { getAutomationSchedulerHealth, recordAutomationSchedulerHealth, type AutomationSchedulerHealth, } from "./scheduler-health.js";
|
|
4
4
|
export { RECURRING_JOBS_SWEEP_PATH, RECURRING_JOBS_SWEEP_TOKEN_SUBJECT, } from "./scheduler-dispatch.js";
|
|
5
5
|
export { createJobTools } from "./tools.js";
|
|
6
|
+
export { AUTOMATION_RUN_MIGRATIONS, runAutomationRunMigrations, } from "./run-history.js";
|
package/dist/jobs/index.js
CHANGED
|
@@ -3,3 +3,4 @@ export { processRecurringJobs, parseJobFrontmatter, buildJobContent, } from "./s
|
|
|
3
3
|
export { getAutomationSchedulerHealth, recordAutomationSchedulerHealth, } from "./scheduler-health.js";
|
|
4
4
|
export { RECURRING_JOBS_SWEEP_PATH, RECURRING_JOBS_SWEEP_TOKEN_SUBJECT, } from "./scheduler-dispatch.js";
|
|
5
5
|
export { createJobTools } from "./tools.js";
|
|
6
|
+
export { AUTOMATION_RUN_MIGRATIONS, runAutomationRunMigrations, } from "./run-history.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type MigrationEntry } from "../db/migrations.js";
|
|
1
2
|
/**
|
|
2
3
|
* "interrupted" is derived at read time, never stored: a process killed
|
|
3
4
|
* mid-run cannot write its own outcome, so a row left running long past the
|
|
@@ -30,6 +31,9 @@ export interface StartAutomationRunInput {
|
|
|
30
31
|
/** A pre-created row still waiting for its background worker handoff. */
|
|
31
32
|
dispatchPending?: boolean;
|
|
32
33
|
}
|
|
34
|
+
/** Authoritative release-time schema for durable automation history. */
|
|
35
|
+
export declare const AUTOMATION_RUN_MIGRATIONS: MigrationEntry[];
|
|
36
|
+
export declare function runAutomationRunMigrations(nitroApp: unknown): Promise<void>;
|
|
33
37
|
/**
|
|
34
38
|
* Record an automation execution. Manual runs create the row before dispatch,
|
|
35
39
|
* so `dispatchPending` distinguishes that durable handoff from a run that has
|
package/dist/jobs/run-history.js
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getDbExec, intType, isPostgres } from "../db/client.js";
|
|
4
4
|
import { ensureColumnExists, ensureIndexExists, ensureTableExists, } from "../db/ddl-guard.js";
|
|
5
|
+
import { runMigrations } from "../db/migrations.js";
|
|
5
6
|
import { emit as emitBusEvent, registerEvent } from "../event-bus/index.js";
|
|
6
7
|
registerEvent({
|
|
7
8
|
name: "automation.run.finished",
|
|
@@ -32,6 +33,49 @@ const INTERRUPTED_RUN_MESSAGE = "Worker stopped before a terminal result was rec
|
|
|
32
33
|
const CLAIM_LEASE_MS = RUN_LIVENESS_CEILING_MS;
|
|
33
34
|
/** Rows kept per automation, so a per-minute schedule cannot grow forever. */
|
|
34
35
|
const RUNS_RETAINED_PER_AUTOMATION = 50;
|
|
36
|
+
/** Authoritative release-time schema for durable automation history. */
|
|
37
|
+
export const AUTOMATION_RUN_MIGRATIONS = [
|
|
38
|
+
{
|
|
39
|
+
version: 1,
|
|
40
|
+
name: "automation-runs-table",
|
|
41
|
+
sql: `
|
|
42
|
+
CREATE TABLE IF NOT EXISTS ${TABLE} (
|
|
43
|
+
id TEXT PRIMARY KEY,
|
|
44
|
+
owner TEXT NOT NULL,
|
|
45
|
+
automation TEXT NOT NULL,
|
|
46
|
+
path TEXT NOT NULL,
|
|
47
|
+
scope TEXT,
|
|
48
|
+
org_id TEXT,
|
|
49
|
+
run_id TEXT,
|
|
50
|
+
thread_id TEXT,
|
|
51
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
52
|
+
started_at INTEGER NOT NULL,
|
|
53
|
+
finished_at INTEGER,
|
|
54
|
+
error TEXT,
|
|
55
|
+
claimed_at INTEGER,
|
|
56
|
+
dispatch_pending INTEGER NOT NULL DEFAULT 0
|
|
57
|
+
);
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_${TABLE}_owner_automation
|
|
59
|
+
ON ${TABLE} (owner, automation, started_at)
|
|
60
|
+
`,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
version: 2,
|
|
64
|
+
name: "automation-runs-claimed-at",
|
|
65
|
+
sql: `ALTER TABLE ${TABLE} ADD COLUMN IF NOT EXISTS claimed_at INTEGER`,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
version: 3,
|
|
69
|
+
name: "automation-runs-dispatch-pending",
|
|
70
|
+
sql: `ALTER TABLE ${TABLE}
|
|
71
|
+
ADD COLUMN IF NOT EXISTS dispatch_pending INTEGER NOT NULL DEFAULT 0`,
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
export async function runAutomationRunMigrations(nitroApp) {
|
|
75
|
+
await runMigrations(AUTOMATION_RUN_MIGRATIONS, {
|
|
76
|
+
table: "_automation_run_migrations",
|
|
77
|
+
})(nitroApp);
|
|
78
|
+
}
|
|
35
79
|
let _initPromise;
|
|
36
80
|
async function ensureTable() {
|
|
37
81
|
if (!_initPromise) {
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
|
|
14
14
|
count: number;
|
|
15
15
|
updated?: undefined;
|
|
16
|
-
error?: undefined;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error?: undefined;
|
|
18
18
|
} | {
|
|
19
19
|
count?: undefined;
|
|
20
20
|
updated: number;
|
|
21
|
-
error?: undefined;
|
|
22
21
|
ok?: undefined;
|
|
22
|
+
error?: undefined;
|
|
23
23
|
} | {
|
|
24
24
|
count?: undefined;
|
|
25
25
|
updated?: undefined;
|
|
@@ -28,6 +28,6 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
|
|
|
28
28
|
} | {
|
|
29
29
|
count?: undefined;
|
|
30
30
|
updated?: undefined;
|
|
31
|
-
error?: undefined;
|
|
32
31
|
ok: boolean;
|
|
32
|
+
error?: undefined;
|
|
33
33
|
}>>;
|
|
@@ -41,16 +41,16 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
-
error?: undefined;
|
|
45
44
|
summary: import("./types.js").TraceSummary;
|
|
46
45
|
spans: import("./types.js").TraceSpan[];
|
|
47
46
|
id?: undefined;
|
|
47
|
+
error?: undefined;
|
|
48
48
|
ok?: undefined;
|
|
49
49
|
} | {
|
|
50
|
-
error?: undefined;
|
|
51
50
|
summary?: undefined;
|
|
52
51
|
spans?: undefined;
|
|
53
52
|
id: string;
|
|
53
|
+
error?: undefined;
|
|
54
54
|
ok?: undefined;
|
|
55
55
|
} | {
|
|
56
56
|
summary?: undefined;
|
|
@@ -59,9 +59,9 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
59
59
|
error: any;
|
|
60
60
|
ok?: undefined;
|
|
61
61
|
} | {
|
|
62
|
-
error?: undefined;
|
|
63
62
|
summary?: undefined;
|
|
64
63
|
spans?: undefined;
|
|
65
64
|
id?: undefined;
|
|
65
|
+
error?: undefined;
|
|
66
66
|
ok: boolean;
|
|
67
67
|
}>>;
|