@agent-native/core 0.145.7 → 0.146.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.
Files changed (186) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +5 -1
  3. package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +37 -11
  4. package/corpus/templates/analytics/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  5. package/corpus/templates/analytics/actions/get-sql-dashboard.ts +20 -4
  6. package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +59 -24
  7. package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +3 -2
  8. package/corpus/templates/analytics/changelog/2026-08-07-analytics-migrations-now-use-bounded-pressure-aware-backgrou.md +6 -0
  9. package/corpus/templates/analytics/changelog/2026-08-07-settings-skeleton-placeholders.md +6 -0
  10. package/corpus/templates/analytics/changelog/2026-08-07-template-activity-charts-now-show-only-core-agent-native-app.md +6 -0
  11. package/corpus/templates/analytics/netlify.toml +1 -1
  12. package/corpus/templates/analytics/package.json +1 -0
  13. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +110 -0
  14. package/corpus/templates/analytics/scripts/migrate-production.ts +24 -0
  15. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +13 -13
  16. package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +448 -0
  17. package/corpus/templates/analytics/server/jobs/analytics-rollup-backfill.ts +11 -2
  18. package/corpus/templates/analytics/server/lib/analytics-rollup-lock.ts +4 -5
  19. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +35 -0
  20. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +3 -5
  21. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +10 -4
  22. package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +4 -11
  23. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +74 -28
  24. package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +2 -5
  25. package/corpus/templates/analytics/server/plugins/db.ts +56 -3
  26. package/corpus/templates/analytics/server/routes/api/analytics-bigquery/backfill.post.ts +55 -0
  27. package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +22 -4
  28. package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +5 -1
  29. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +37 -11
  30. package/corpus/templates/assets/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  31. package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +5 -1
  32. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +37 -11
  33. package/corpus/templates/brain/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  34. package/corpus/templates/brain/server/lib/connectors.ts +38 -14
  35. package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +5 -1
  36. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +37 -11
  37. package/corpus/templates/calendar/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  38. package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
  39. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
  40. package/corpus/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  41. package/corpus/templates/chat/AGENTS.md +3 -0
  42. package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +5 -1
  43. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +37 -11
  44. package/corpus/templates/clips/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  45. package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +5 -1
  46. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +37 -11
  47. package/corpus/templates/content/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  48. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +37 -11
  49. package/corpus/templates/crm/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  50. package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +21 -3
  51. package/corpus/templates/crm/app/components/crm/settings/ConnectionSettings.tsx +23 -5
  52. package/corpus/templates/crm/app/components/crm/settings/FieldsSettings.tsx +24 -5
  53. package/corpus/templates/crm/app/components/crm/settings/ListsSettings.tsx +23 -3
  54. package/corpus/templates/crm/changelog/2026-08-07-settings-loading-skeletons.md +6 -0
  55. package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +5 -1
  56. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +37 -11
  57. package/corpus/templates/design/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  58. package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +5 -1
  59. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +37 -11
  60. package/corpus/templates/dispatch/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  61. package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +5 -1
  62. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +37 -11
  63. package/corpus/templates/factory/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  64. package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +5 -1
  65. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +37 -11
  66. package/corpus/templates/forms/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  67. package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +5 -1
  68. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +37 -11
  69. package/corpus/templates/macros/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  70. package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +5 -1
  71. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +37 -11
  72. package/corpus/templates/mail/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  73. package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +5 -1
  74. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +37 -11
  75. package/corpus/templates/plan/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  76. package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +5 -1
  77. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +37 -11
  78. package/corpus/templates/slides/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  79. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +13 -0
  80. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -0
  81. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +5 -0
  82. package/corpus/templates/slides/app/components/presentation/PresenterView.tsx +5 -0
  83. package/corpus/templates/slides/app/pages/DeckEditor.tsx +3 -0
  84. package/corpus/templates/slides/app/pages/Presentation.tsx +3 -0
  85. package/corpus/templates/slides/app/pages/SharedPresentation.tsx +5 -0
  86. package/corpus/templates/slides/app/routes/slide.tsx +10 -5
  87. package/corpus/templates/slides/changelog/2026-08-07-slide-previews-and-present-now-keep-deck-colors-consistent.md +6 -0
  88. package/corpus/templates/slides/server/db/schema.ts +1 -0
  89. package/corpus/templates/slides/server/handlers/share.ts +51 -3
  90. package/corpus/templates/slides/server/plugins/db.ts +5 -0
  91. package/corpus/templates/slides/shared/api.ts +2 -0
  92. package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +5 -1
  93. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +37 -11
  94. package/corpus/templates/tasks/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  95. package/dist/cli/code-agent-executor.js +8 -3
  96. package/dist/client/ConnectBuilderCard.d.ts +7 -7
  97. package/dist/client/ConnectBuilderCard.js +45 -20
  98. package/dist/client/components/ApiKeySettings.js +2 -5
  99. package/dist/client/org/TeamPage.js +4 -2
  100. package/dist/client/settings/AccountSettingsCard.js +2 -1
  101. package/dist/client/settings/AgentsSection.js +2 -1
  102. package/dist/client/settings/SecretsSection.js +3 -2
  103. package/dist/client/settings/SettingsPanel.js +6 -12
  104. package/dist/client/settings/SettingsSkeleton.d.ts +12 -0
  105. package/dist/client/settings/SettingsSkeleton.js +12 -0
  106. package/dist/client/settings/VoiceTranscriptionSection.js +5 -4
  107. package/dist/client/settings/index.d.ts +1 -0
  108. package/dist/client/settings/index.js +1 -0
  109. package/dist/config.d.ts +9 -2
  110. package/dist/config.js +38 -2
  111. package/dist/db/client.d.ts +26 -8
  112. package/dist/db/client.js +200 -9
  113. package/dist/db/create-get-db.js +2 -2
  114. package/dist/db/ddl-guard.js +3 -1
  115. package/dist/db/ensure-additive-columns.d.ts +2 -0
  116. package/dist/db/ensure-additive-columns.js +5 -2
  117. package/dist/db/index.d.ts +2 -2
  118. package/dist/db/index.js +2 -2
  119. package/dist/db/migrations.d.ts +11 -54
  120. package/dist/db/migrations.js +70 -0
  121. package/dist/deploy/build.js +5 -1
  122. package/dist/jobs/index.d.ts +1 -0
  123. package/dist/jobs/index.js +1 -0
  124. package/dist/jobs/run-history.d.ts +4 -0
  125. package/dist/jobs/run-history.js +44 -0
  126. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  127. package/dist/scripts/docs/search.js +3 -2
  128. package/dist/secrets/routes.d.ts +6 -6
  129. package/dist/server/agent-chat/prompt-resources.js +4 -3
  130. package/dist/server/agent-chat-plugin.js +6 -4
  131. package/dist/server/agents-bundle.d.ts +20 -3
  132. package/dist/server/agents-bundle.js +57 -30
  133. package/dist/server/auth-plugin.js +5 -0
  134. package/dist/server/better-auth-instance.js +2 -63
  135. package/dist/server/better-auth-migrations.d.ts +8 -0
  136. package/dist/server/better-auth-migrations.js +179 -0
  137. package/dist/server/edge.d.ts +2 -0
  138. package/dist/server/edge.js +2 -0
  139. package/dist/server/index.d.ts +2 -0
  140. package/dist/server/index.js +2 -0
  141. package/dist/server/release-migrations.d.ts +8 -0
  142. package/dist/server/release-migrations.js +24 -0
  143. package/dist/server/sweep-runtime.d.ts +1 -1
  144. package/dist/server/sweep-runtime.js +15 -0
  145. package/dist/shared/runtime-config.js +1 -1
  146. package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
  147. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
  148. package/dist/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  149. package/dist/templates/chat/AGENTS.md +3 -0
  150. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +5 -1
  151. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +37 -11
  152. package/dist/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  153. package/dist/templates/default/AGENTS.md +3 -0
  154. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +37 -11
  155. package/dist/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  156. package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -1
  157. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +37 -11
  158. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  159. package/dist/templates/workspace-core/AGENTS.md +3 -0
  160. package/dist/templates/workspace-root/AGENTS.md +3 -0
  161. package/dist/vite/agent-native-config-loader.d.ts +10 -0
  162. package/dist/vite/agent-native-config-loader.js +56 -0
  163. package/dist/vite/agents-bundle-plugin.d.ts +5 -1
  164. package/dist/vite/agents-bundle-plugin.js +29 -6
  165. package/dist/vite/client.d.ts +4 -2
  166. package/dist/vite/client.js +4 -49
  167. package/dist/vite/index.d.ts +2 -2
  168. package/dist/vite/index.js +1 -1
  169. package/docs/content/agent-native-config.mdx +43 -17
  170. package/docs/content/environment-variables.mdx +1 -1
  171. package/package.json +3 -3
  172. package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
  173. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
  174. package/src/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  175. package/src/templates/chat/AGENTS.md +3 -0
  176. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +5 -1
  177. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +37 -11
  178. package/src/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  179. package/src/templates/default/AGENTS.md +3 -0
  180. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +37 -11
  181. package/src/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  182. package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -1
  183. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +37 -11
  184. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  185. package/src/templates/workspace-core/AGENTS.md +3 -0
  186. package/src/templates/workspace-root/AGENTS.md +3 -0
package/dist/config.js CHANGED
@@ -2,12 +2,12 @@
2
2
  * Public, non-secret configuration for an Agent-Native app.
3
3
  *
4
4
  * This module is intentionally free of Node and framework imports so it can be
5
- * used from a typed `agent-native.ts` file and from browser code after Vite
5
+ * used from a typed `agent-native.config.ts` file and from browser code after Vite
6
6
  * serializes the resolved config into the client bundle.
7
7
  */
8
8
  export const AGENT_NATIVE_CONFIG_VERSION = 1;
9
9
  /**
10
- * Type-safe authoring helper for `agent-native.ts`.
10
+ * Type-safe authoring helper for `agent-native.config.ts`.
11
11
  *
12
12
  * Like Next's typed config file, this is deliberately identity-like: the
13
13
  * framework evaluates the exported object or factory in the Vite config
@@ -27,6 +27,7 @@ export function normalizeAgentNativeConfig(input, source = "agent-native config"
27
27
  const onboardingValue = input.onboarding;
28
28
  const runtimeValue = input.runtime;
29
29
  const diagnosticsValue = input.diagnostics;
30
+ const instructionsValue = input.instructions;
30
31
  const normalized = {
31
32
  ...(input.version === undefined
32
33
  ? {}
@@ -45,6 +46,9 @@ export function normalizeAgentNativeConfig(input, source = "agent-native config"
45
46
  if (diagnosticsValue !== undefined) {
46
47
  normalized.diagnostics = normalizeDiagnosticsConfig(diagnosticsValue, `${source}.diagnostics`);
47
48
  }
49
+ if (instructionsValue !== undefined) {
50
+ normalized.instructions = normalizeInstructionsConfig(instructionsValue, `${source}.instructions`);
51
+ }
48
52
  return normalized;
49
53
  }
50
54
  export function mergeAgentNativeConfigs(base, override) {
@@ -91,6 +95,12 @@ export function mergeAgentNativeConfigs(base, override) {
91
95
  ...override.diagnostics,
92
96
  }
93
97
  : undefined,
98
+ instructions: base.instructions || override.instructions
99
+ ? {
100
+ ...base.instructions,
101
+ ...override.instructions,
102
+ }
103
+ : undefined,
94
104
  };
95
105
  }
96
106
  export function resolveAgentNativeConfig(input, context) {
@@ -175,6 +185,32 @@ function normalizeDiagnosticsConfig(value, source) {
175
185
  ? {}
176
186
  : { failOnBuild: value.failOnBuild };
177
187
  }
188
+ function normalizeInstructionsConfig(value, source) {
189
+ if (!isRecord(value)) {
190
+ throw new Error(`${source} must be an object`);
191
+ }
192
+ const result = {};
193
+ for (const audience of ["runtime", "development"]) {
194
+ const pathValue = value[audience];
195
+ if (pathValue === undefined)
196
+ continue;
197
+ if (typeof pathValue !== "string") {
198
+ throw new Error(`${source}.${audience} must be a relative file path`);
199
+ }
200
+ result[audience] = normalizeRelativeFilePath(pathValue, `${source}.${audience}`);
201
+ }
202
+ return result;
203
+ }
204
+ function normalizeRelativeFilePath(value, source) {
205
+ const normalized = value.trim().replaceAll("\\", "/");
206
+ if (!normalized ||
207
+ normalized.startsWith("/") ||
208
+ /^[A-Za-z]:\//.test(normalized) ||
209
+ normalized.split("/").some((segment) => segment === "..")) {
210
+ throw new Error(`${source} must be a non-empty relative file path inside the app root`);
211
+ }
212
+ return normalized;
213
+ }
178
214
  function normalizeRequiredEnvKeys(value, source) {
179
215
  if (value === undefined)
180
216
  return undefined;
@@ -174,14 +174,20 @@ export declare function withDbTimeout<T>(op: string, run: () => Promise<T>, ms?:
174
174
  */
175
175
  export declare function isServerlessRuntime(): boolean;
176
176
  /**
177
- * postgres.js pool options tuned per runtime. idle_timeout is shortened on
178
- * serverless so a thawed-but-idle instance releases its connections quickly.
179
- * Long-lived Node servers keep the larger pool for throughput.
180
- *
181
- * The cap assumes {@link sharedDbPool}: one pool per URL for the whole
182
- * process, not one per consumer. See {@link neonPoolMax} for why the cap must
183
- * leave room for concurrency.
177
+ * True for production serverless execution contexts. Netlify also exposes
178
+ * `NETLIFY=true` in its build environment, so build-time code that owns
179
+ * migrations must use `withMigrationRuntime()` explicitly.
180
+ */
181
+ export declare function isProductionServerlessFunctionRuntime(env?: NodeJS.ProcessEnv): boolean;
182
+ /** True for statements that change database schema or database privileges. */
183
+ export declare function isSchemaMutationStatement(statement: DbExecStatement): boolean;
184
+ /**
185
+ * Request functions must never be able to prepare or mutate schema. Throwing
186
+ * here makes an unconverted ensureTable path fail loudly instead of silently
187
+ * reporting success against a missing table. The release migration wrapper
188
+ * is the only supported production opt-in.
184
189
  */
190
+ export declare function assertSchemaMutationAllowed(statement: DbExecStatement): void;
185
191
  export declare function pgPoolOptions(url: string): Record<string, unknown>;
186
192
  /**
187
193
  * Shared options for every Neon serverless pool. The startup parameter is
@@ -221,7 +227,19 @@ export declare function isBackgroundFunctionPoolContext(): boolean;
221
227
  * actual context.
222
228
  */
223
229
  export declare function describeDbError(err: unknown): string;
224
- export declare function attachNeonPoolErrorLogger(pool: unknown, label?: string): void;
230
+ /**
231
+ * Thrown INSTEAD of attempting, while an endpoint is in cooldown.
232
+ *
233
+ * The refused attempt's error is described in the message and deliberately NOT
234
+ * attached as `cause`: isConnectionError() walks `cause.code`/`cause.message`,
235
+ * so a cause would reclassify this as retryable and retryOnConnectionError
236
+ * would drive the storm straight back.
237
+ */
238
+ export declare class DbConnectCooldownError extends Error {
239
+ code: string;
240
+ constructor(remainingMs: number, refusedBy: string);
241
+ }
242
+ export declare function guardNeonPool(pool: unknown, url: string, label?: string): void;
225
243
  interface ClosablePool {
226
244
  end(): Promise<unknown>;
227
245
  }
package/dist/db/client.js CHANGED
@@ -613,6 +613,9 @@ export function isTransientDatabaseError(err) {
613
613
  const error = err;
614
614
  const code = String(error?.code ?? error?.cause?.code ?? "");
615
615
  if (code === "ECHECKOUTTIMEOUT" ||
616
+ // Shed load during a connect cooldown is transient by construction — the
617
+ // endpoint refused, we chose not to re-ask yet. Surfaces as 503, not 500.
618
+ code === "DB_CONNECT_COOLDOWN" ||
616
619
  code === "EMAXCONN" ||
617
620
  code === "53300" ||
618
621
  code === "57014" ||
@@ -765,6 +768,47 @@ export function isServerlessRuntime() {
765
768
  !!process.env.LAMBDA_TASK_ROOT ||
766
769
  !!process.env.CF_PAGES);
767
770
  }
771
+ /**
772
+ * True for production serverless execution contexts. Netlify also exposes
773
+ * `NETLIFY=true` in its build environment, so build-time code that owns
774
+ * migrations must use `withMigrationRuntime()` explicitly.
775
+ */
776
+ export function isProductionServerlessFunctionRuntime(env = process.env) {
777
+ if (env.NODE_ENV !== "production" || env.NETLIFY_LOCAL === "true") {
778
+ return false;
779
+ }
780
+ return Boolean(env.NETLIFY === "true" ||
781
+ env.NETLIFY_FUNCTION_NAME ||
782
+ env.AWS_LAMBDA_FUNCTION_NAME ||
783
+ env.AWS_LAMBDA_FUNCTION_VERSION ||
784
+ env.LAMBDA_TASK_ROOT ||
785
+ env.AWS_EXECUTION_ENV?.startsWith("AWS_Lambda") === true ||
786
+ env.VERCEL_FUNCTION_ID ||
787
+ env.VERCEL_REGION ||
788
+ env.VERCEL === "1");
789
+ }
790
+ const SCHEMA_MUTATION_STATEMENT = /^\s*(?:CREATE|ALTER|DROP|TRUNCATE|COMMENT|REINDEX|GRANT|REVOKE)\b/i;
791
+ function rawSql(statement) {
792
+ return typeof statement === "string" ? statement : statement.sql;
793
+ }
794
+ /** True for statements that change database schema or database privileges. */
795
+ export function isSchemaMutationStatement(statement) {
796
+ return SCHEMA_MUTATION_STATEMENT.test(rawSql(statement));
797
+ }
798
+ /**
799
+ * Request functions must never be able to prepare or mutate schema. Throwing
800
+ * here makes an unconverted ensureTable path fail loudly instead of silently
801
+ * reporting success against a missing table. The release migration wrapper
802
+ * is the only supported production opt-in.
803
+ */
804
+ export function assertSchemaMutationAllowed(statement) {
805
+ const migrationRuntime = globalThis.__AGENT_NATIVE_MIGRATION_RUNTIME__ === true;
806
+ if (isProductionServerlessFunctionRuntime() &&
807
+ !migrationRuntime &&
808
+ isSchemaMutationStatement(statement)) {
809
+ throw new Error("Schema mutation attempted in a production serverless request. Run migrations in the release job instead.");
810
+ }
811
+ }
768
812
  /**
769
813
  * postgres.js pool options tuned per runtime. idle_timeout is shortened on
770
814
  * serverless so a thawed-but-idle instance releases its connections quickly.
@@ -774,10 +818,27 @@ export function isServerlessRuntime() {
774
818
  * process, not one per consumer. See {@link neonPoolMax} for why the cap must
775
819
  * leave room for concurrency.
776
820
  */
821
+ /**
822
+ * Identifies the connection in `pg_stat_activity.application_name`.
823
+ *
824
+ * Without it every backend reports `pgbouncer` and a runaway query is
825
+ * anonymous. A 58 MB `SELECT id, config FROM dashboards` ran 20-wide against
826
+ * production and could not be traced to a caller: it appears nowhere in the
827
+ * repo or any built bundle, and `pg_stat_statements` is not installed. This is
828
+ * the cheapest thing that would have named it.
829
+ */
830
+ function poolApplicationName() {
831
+ const site = process.env.SITE_NAME ??
832
+ process.env.NETLIFY_SITE_NAME ??
833
+ process.env.AGENT_NATIVE_APP_NAME ??
834
+ "app";
835
+ return `agent-native:${site}`.slice(0, 63);
836
+ }
777
837
  export function pgPoolOptions(url) {
778
838
  const serverless = isServerlessRuntime();
779
839
  return {
780
840
  onnotice: () => { },
841
+ connection: { application_name: poolApplicationName() },
781
842
  max: serverless ? serverlessPoolMax() : 20,
782
843
  idle_timeout: serverless ? 20 : 240,
783
844
  max_lifetime: 60 * 30,
@@ -785,6 +846,7 @@ export function pgPoolOptions(url) {
785
846
  ...(serverless
786
847
  ? {
787
848
  connection: {
849
+ application_name: poolApplicationName(),
788
850
  idle_in_transaction_session_timeout: 30_000,
789
851
  },
790
852
  }
@@ -901,7 +963,77 @@ export function describeDbError(err) {
901
963
  }
902
964
  return String(err);
903
965
  }
904
- export function attachNeonPoolErrorLogger(pool, label = "db/neon") {
966
+ /**
967
+ * How long one endpoint stops ATTEMPTING to connect after an attempt failed.
968
+ * Jittered so concurrent function instances do not re-probe in lockstep.
969
+ */
970
+ function connectCooldownMs() {
971
+ const raw = Number(process.env.DB_CONNECT_COOLDOWN_MS);
972
+ const base = Number.isFinite(raw) && raw > 0 ? raw : 2_000;
973
+ return Math.round(base * (0.5 + Math.random()));
974
+ }
975
+ /**
976
+ * Thrown INSTEAD of attempting, while an endpoint is in cooldown.
977
+ *
978
+ * The refused attempt's error is described in the message and deliberately NOT
979
+ * attached as `cause`: isConnectionError() walks `cause.code`/`cause.message`,
980
+ * so a cause would reclassify this as retryable and retryOnConnectionError
981
+ * would drive the storm straight back.
982
+ */
983
+ export class DbConnectCooldownError extends Error {
984
+ code = "DB_CONNECT_COOLDOWN";
985
+ constructor(remainingMs, refusedBy) {
986
+ super(`Database is refusing connection attempts; not attempting again for ${remainingMs}ms. Last failure: ${refusedBy}`);
987
+ this.name = "DbConnectCooldownError";
988
+ }
989
+ }
990
+ const connectCooldowns = new Map();
991
+ /**
992
+ * Neon rejects a connection ATTEMPT, not a connection: "Failed to acquire
993
+ * permit to connect to the database. Too many database connection attempts are
994
+ * currently ongoing." A failed acquire leaves the pool with zero idle clients,
995
+ * so the next execute() calls connect() again — and retryOnConnectionError
996
+ * backs off only 100ms. The process answers a refusal by manufacturing the next
997
+ * attempt, which is what keeps the refusal true. Production sat in this loop
998
+ * until the compute was restarted by hand.
999
+ *
1000
+ * Hold a short cooldown per endpoint so a refused attempt cannot produce the
1001
+ * next one. Checking out an ALREADY-IDLE client is not an attempt and passes
1002
+ * through, so a cooldown degrades throughput instead of taking the process
1003
+ * offline.
1004
+ */
1005
+ function gateNeonConnect(pool, url, label) {
1006
+ const connect = pool.connect;
1007
+ if (typeof connect !== "function")
1008
+ return;
1009
+ const gated = function gatedConnect(...args) {
1010
+ const idle = typeof pool.idleCount === "number" ? pool.idleCount : 0;
1011
+ const gate = connectCooldowns.get(url);
1012
+ if (idle === 0 && gate && Date.now() < gate.until) {
1013
+ return Promise.reject(new DbConnectCooldownError(gate.until - Date.now(), gate.refusedBy));
1014
+ }
1015
+ return Promise.resolve(connect.apply(this, args)).then((client) => {
1016
+ connectCooldowns.delete(url);
1017
+ return client;
1018
+ }, (err) => {
1019
+ const refusedBy = describeDbError(err);
1020
+ const ms = connectCooldownMs();
1021
+ // One line per cooldown window, not per shed request.
1022
+ if (!gate || Date.now() >= gate.until) {
1023
+ console.warn(`[${label}] connection attempt refused; pausing attempts ${ms}ms:`, refusedBy);
1024
+ }
1025
+ connectCooldowns.set(url, { until: Date.now() + ms, refusedBy });
1026
+ throw err;
1027
+ });
1028
+ };
1029
+ // Carry the wrapped function's own properties onto the wrapper. A pool's
1030
+ // `connect` may be instrumented — metrics, tracing, a test spy — and
1031
+ // replacing it with a bare closure would silently drop that instrumentation
1032
+ // along with any assertions built on it.
1033
+ Object.assign(gated, connect);
1034
+ pool.connect = gated;
1035
+ }
1036
+ export function guardNeonPool(pool, url, label = "db/neon") {
905
1037
  if (!pool || typeof pool !== "object")
906
1038
  return;
907
1039
  if (loggedNeonPools.has(pool))
@@ -910,6 +1042,7 @@ export function attachNeonPoolErrorLogger(pool, label = "db/neon") {
910
1042
  if (typeof withEvents.on !== "function")
911
1043
  return;
912
1044
  loggedNeonPools.add(pool);
1045
+ gateNeonConnect(pool, url, label);
913
1046
  withEvents.on("error", (err) => {
914
1047
  console.warn(`[${label}] pool error (will reconnect on next query):`, describeDbError(err));
915
1048
  });
@@ -1118,7 +1251,7 @@ async function createDbExecInternal(config = {}, trackSingletonResources = false
1118
1251
  const pool = trackSingletonResources
1119
1252
  ? sharedDbPool("neon", url, makePool)
1120
1253
  : makePool();
1121
- attachNeonPoolErrorLogger(pool);
1254
+ guardNeonPool(pool, url);
1122
1255
  const httpSql = bgHttp ? neon(url, { fullResults: true }) : null;
1123
1256
  async function queryNeonClient(client, sql, timeoutOverrideMs) {
1124
1257
  const { rawSql, args } = sqlAndArgs(sql);
@@ -1504,7 +1637,36 @@ async function createDbExecInternal(config = {}, trackSingletonResources = false
1504
1637
  };
1505
1638
  }
1506
1639
  export async function createDbExec(config = {}) {
1507
- return createDbExecInternal(config, false);
1640
+ const exec = await createDbExecInternal(config, false);
1641
+ return guardSchemaMutations(exec);
1642
+ }
1643
+ function guardSchemaMutations(exec) {
1644
+ const guarded = {
1645
+ async execute(statement) {
1646
+ assertSchemaMutationAllowed(statement);
1647
+ return exec.execute(statement);
1648
+ },
1649
+ };
1650
+ if (exec.atomicBatch) {
1651
+ guarded.atomicBatch = async (statements) => {
1652
+ for (const statement of statements) {
1653
+ assertSchemaMutationAllowed(statement);
1654
+ }
1655
+ return exec.atomicBatch(statements);
1656
+ };
1657
+ }
1658
+ if (exec.transaction) {
1659
+ guarded.transaction = (fn) => exec.transaction((tx) => fn({
1660
+ ...tx,
1661
+ execute: async (statement) => {
1662
+ assertSchemaMutationAllowed(statement);
1663
+ return tx.execute(statement);
1664
+ },
1665
+ }));
1666
+ }
1667
+ if (exec.close)
1668
+ guarded.close = () => exec.close();
1669
+ return guarded;
1508
1670
  }
1509
1671
  async function initClient() {
1510
1672
  if (_exec)
@@ -1561,6 +1723,7 @@ export function getDbExec() {
1561
1723
  return sql;
1562
1724
  }
1563
1725
  async function execAnnotated(s) {
1726
+ assertSchemaMutationAllowed(s);
1564
1727
  try {
1565
1728
  return await _exec.execute(sanitize(s));
1566
1729
  }
@@ -1571,6 +1734,7 @@ export function getDbExec() {
1571
1734
  // Return a proxy that lazy-inits on first call
1572
1735
  const proxy = {
1573
1736
  async execute(sql) {
1737
+ assertSchemaMutationAllowed(sql);
1574
1738
  if (!_initPromise)
1575
1739
  _initPromise = initClient();
1576
1740
  try {
@@ -1588,11 +1752,19 @@ export function getDbExec() {
1588
1752
  const wrapper = {
1589
1753
  execute: (s) => execAnnotated(s),
1590
1754
  atomicBatch: _exec.atomicBatch
1591
- ? (statements) => _exec.atomicBatch(statements.map((s) => sanitize(s)))
1755
+ ? async (statements) => {
1756
+ for (const statement of statements) {
1757
+ assertSchemaMutationAllowed(statement);
1758
+ }
1759
+ return _exec.atomicBatch(statements.map((s) => sanitize(s)));
1760
+ }
1592
1761
  : undefined,
1593
1762
  transaction: _exec.transaction
1594
1763
  ? (fn) => _exec.transaction((tx) => fn({
1595
- execute: (s) => tx.execute(sanitize(s)),
1764
+ execute: (s) => {
1765
+ assertSchemaMutationAllowed(s);
1766
+ return tx.execute(sanitize(s));
1767
+ },
1596
1768
  transaction: tx.transaction,
1597
1769
  }))
1598
1770
  : undefined,
@@ -1614,11 +1786,19 @@ export function getDbExec() {
1614
1786
  const wrapper = {
1615
1787
  execute: (s) => execAnnotated(s),
1616
1788
  atomicBatch: _exec.atomicBatch
1617
- ? (statements) => _exec.atomicBatch(statements.map((s) => sanitize(s)))
1789
+ ? async (statements) => {
1790
+ for (const statement of statements) {
1791
+ assertSchemaMutationAllowed(statement);
1792
+ }
1793
+ return _exec.atomicBatch(statements.map((s) => sanitize(s)));
1794
+ }
1618
1795
  : undefined,
1619
1796
  transaction: _exec.transaction
1620
1797
  ? (innerFn) => _exec.transaction((tx) => innerFn({
1621
- execute: (s) => tx.execute(sanitize(s)),
1798
+ execute: (s) => {
1799
+ assertSchemaMutationAllowed(s);
1800
+ return tx.execute(sanitize(s));
1801
+ },
1622
1802
  transaction: tx.transaction,
1623
1803
  }))
1624
1804
  : undefined,
@@ -1626,7 +1806,10 @@ export function getDbExec() {
1626
1806
  Object.assign(proxy, wrapper);
1627
1807
  if (_exec.transaction) {
1628
1808
  return _exec.transaction((tx) => fn({
1629
- execute: (s) => tx.execute(sanitize(s)),
1809
+ execute: (s) => {
1810
+ assertSchemaMutationAllowed(s);
1811
+ return tx.execute(sanitize(s));
1812
+ },
1630
1813
  transaction: tx.transaction,
1631
1814
  }));
1632
1815
  }
@@ -1636,6 +1819,9 @@ export function getDbExec() {
1636
1819
  return explicitTransaction(wrapper.execute)(fn);
1637
1820
  },
1638
1821
  async atomicBatch(statements) {
1822
+ for (const statement of statements) {
1823
+ assertSchemaMutationAllowed(statement);
1824
+ }
1639
1825
  if (!_initPromise)
1640
1826
  _initPromise = initClient();
1641
1827
  try {
@@ -1649,7 +1835,12 @@ export function getDbExec() {
1649
1835
  if (!_exec.atomicBatch) {
1650
1836
  throw new Error("This database does not support atomic batches.");
1651
1837
  }
1652
- const batch = (items) => _exec.atomicBatch(items.map((item) => sanitize(item)));
1838
+ const batch = async (items) => {
1839
+ for (const item of items) {
1840
+ assertSchemaMutationAllowed(item);
1841
+ }
1842
+ return _exec.atomicBatch(items.map((item) => sanitize(item)));
1843
+ };
1653
1844
  Object.assign(proxy, { atomicBatch: batch });
1654
1845
  return batch(statements);
1655
1846
  },
@@ -1,6 +1,6 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { drizzle as drizzleD1 } from "drizzle-orm/d1";
3
- import { getDialect, getCloudflareD1Binding, getDatabaseUrl, getDatabaseAuthToken, isLocalSqliteUrl, isPgliteUrl, isConnectionError, getPgliteClient, loadPgliteDrizzle, prepareLocalSqliteUrl, sqliteFilenameFromUrl, pgPoolOptions, neonPoolOptions, attachNeonPoolErrorLogger, withDbTimeout, retryOnConnectionError, dbOpTimeoutMs, sharedDbPool, onSharedDbPoolsClosed, onSharedDbPoolReplaced, } from "./client.js";
3
+ import { getDialect, getCloudflareD1Binding, getDatabaseUrl, getDatabaseAuthToken, isLocalSqliteUrl, isPgliteUrl, isConnectionError, getPgliteClient, loadPgliteDrizzle, prepareLocalSqliteUrl, sqliteFilenameFromUrl, pgPoolOptions, neonPoolOptions, guardNeonPool, withDbTimeout, retryOnConnectionError, dbOpTimeoutMs, sharedDbPool, onSharedDbPoolsClosed, onSharedDbPoolReplaced, } from "./client.js";
4
4
  // Lazy driver loaders — cached promises so dynamic import only runs once.
5
5
  let _pgDrizzle;
6
6
  function getPgDrizzle() {
@@ -408,7 +408,7 @@ export function createGetDb(schema) {
408
408
  // schema module. See `sharedDbPool` in client.ts.
409
409
  resetOnPoolClose("neon", url);
410
410
  const rawPool = sharedDbPool("neon", url, () => new Pool({ connectionString: url, ...neonPoolOptions() }));
411
- attachNeonPoolErrorLogger(rawPool);
411
+ guardNeonPool(rawPool, url);
412
412
  // Wrap the pool with the resilience layer so Drizzle queries get the
413
413
  // same withDbTimeout + retryOnConnectionError protection as the raw
414
414
  // DbExec path in client.ts. Reads retry freely; writes only retry on
@@ -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, getDbExec } from "./client.js";
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 {
@@ -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";
@@ -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
- * Runs a list of migrations against the configured database, gated by a
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
- * New migrations should always set a `name`. Legacy unnamed migrations don't
138
- * need to be renamed retroactively — the two gating strategies coexist in the
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 {};